FastReport_2022_VCL/Source/frxGDIPKerning.pas
2024-01-01 16:13:08 +01:00

576 lines
15 KiB
ObjectPascal

{******************************************************************}
{ GDIPKerning }
{ }
{ home page : http://www.mwcs.de }
{ email : martin.walter@mwcs.de }
{ }
{ date : 20-11-2007 }
{ }
{ version : 1.1 }
{ }
{ Use of this file is permitted for commercial and non-commercial }
{ use, as long as the author is credited. }
{ This file (c) 2007 Martin Walter }
{ }
{ This Software is distributed on an "AS IS" basis, WITHOUT }
{ WARRANTY OF ANY KIND, either express or implied. }
{ }
{ *****************************************************************}
{ Adapted for FR 7 by Oleg Adibekov }
{ *****************************************************************}
unit frxGDIPKerning;
interface
{$I frx.inc}
uses
Windows,
Classes,
{$IFDEF Delphi16}
Winapi.GDIPOBJ,
Winapi.GDIPAPI,
{$ELSE}
frxGDIPOBJ,
frxGDIPAPI,
{$ENDIF}
Types,
frxSVGCanvas;
type
TKerningPairs = array of TKerningPair;
PKerningPairs = ^TKerningPairs;
TKerning = record
Chars: Cardinal;
Cell: Integer;
Kerning: Integer;
end;
PKerning = ^TKerning;
TGPKerningText = class(TObject)
private
FKerningList: TList;
FKerningPairs: TKerningPairs;
FPairCount: Integer;
FLastFont: WideString;
FLastWeight: Integer;
FLastStyle: Integer;
FFontSizeFactor: Double;
FFont: HFont;
FOldFont: HFont;
FDC: HDC;
FGPFont: TGPFont;
procedure PrepareDC;
procedure UnprepareDC;
procedure PrepareFont(const LF: TLogFontW);
procedure UnprepareFont;
procedure PrepareKerning(const Font: TGPFont); overload;
procedure PrepareKerning(const Font: TGPFont; const Graphics: TGPGraphics;
WithFactor: Boolean); overload;
procedure UnprepareKerning;
procedure ClearKerningList;
procedure AddToKerningList(const First, Second: Word;
Cell, Kerning: Integer);
function IndexOfKerning(const First, Second: Word): Integer;
function GetDistance(const Index: Integer;
const DistanceFactor, KerningFactor: Single): Single;
procedure Clear;
function AddGlyphToPath(const Path: TGPGraphicsPath; const Char: WideChar;
const Family: TGPFontFamily; const Style: Integer; const Size: Single;
const Origin: TGPPointF; const Format: TGPStringFormat): TStatus;
function AddGlyphToGraphics(const Graphics: TGPGraphics;
const Char: WideChar; const Font: TGPFont; const Origin: TGPPointF;
const Format: TGPStringFormat; const Brush: TGPBrush): TStatus;
procedure AddDecoration(const Path: TGPGraphicsPath;
const Left, Top, Width: Single; const Font: TGPFont; dY: Single);
public
constructor Create;
destructor Destroy; override;
function AddToPath(const Path, UPath, LPath, OPath: TGPGraphicsPath;
const Text: WideString; const Family: TGPFontFamily; Style: Integer;
const Size: Single; TextOrigin: TTextOrigin;
const DistanceFactor: Single = 1;
const KerningFactor: Single = 1): TStatus;
function AddToGraphics(const Graphics: TGPGraphics;
const Text: WideString; const Font: TGPFont; Origin: TGPPointF;
const Format: TGPStringFormat; const Brush: TGPBrush;
const DistanceFactor: Single = 1;
const KerningFactor: Single = 1): TStatus; overload;
function AddToGraphics(const Graphics: TGPGraphics;
const Text: WideString; const Font: TGPFont; Origin: TGPPoint;
const Format: TGPStringFormat; const Brush: TGPBrush;
const DistanceFactor: Single = 1;
const KerningFactor: Single = 1): TStatus; overload;
function MeasureText(const Text: WideString; const Font: TGPFont;
const DistanceFactor: Single = 1;
const KerningFactor: Single = 1): Single;
function GetCellWidth(const First, Second: Word;
const DistanceFactor: Single = 1;
const KerningFactor: Single = 1): Single;
procedure Prepare(const Family: TGPFontFamily; Style: Integer;
const Size: Single; const Format: TGPStringFormat);
procedure Unprepare;
end;
function KerningText: TGPKerningText;
implementation
var
FKerningText: TGPKerningText;
function KerningText: TGPKerningText;
begin
if not Assigned(FKerningText) then
FKerningText := TGPKerningText.Create;
Result := FKerningText;
end;
{ TKerningText }
procedure TGPKerningText.AddDecoration(const Path: TGPGraphicsPath; const Left,
Top, Width: Single; const Font: TGPFont; dY: Single);
var
YPos, Height: Single;
begin
if Path <> nil then
begin
Height := Font.GetSize / 20;
YPos := Top + Font.GetSize * dY + Height / 2;
Path.SetFillMode(FillModeWinding);
Path.AddRectangle(MakeRect(Left, YPos, Width, Height));
end;
end;
function TGPKerningText.AddGlyphToGraphics(const Graphics: TGPGraphics;
const Char: WideChar; const Font: TGPFont; const Origin: TGPPointF;
const Format: TGPStringFormat; const Brush: TGPBrush): TStatus;
begin
Result := Graphics.DrawString(Char, -1, Font, Origin, Format, Brush);
end;
function TGPKerningText.AddGlyphToPath(const Path: TGPGraphicsPath;
const Char: WideChar; const Family: TGPFontFamily; const Style: Integer;
const Size: Single; const Origin: TGPPointF;
const Format: TGPStringFormat): TStatus;
begin
Result := Path.AddString(Char, -1, Family, Style, Size, Origin, Format);
end;
function TGPKerningText.AddToGraphics(const Graphics: TGPGraphics;
const Text: WideString; const Font: TGPFont; Origin: TGPPointF;
const Format: TGPStringFormat; const Brush: TGPBrush;
const DistanceFactor: Single = 1; const KerningFactor: Single = 1): TStatus;
var
P1, P2: PWideChar;
Status: TStatus;
begin
Status := Ok;
if Text = '' then
begin
Result := Ok;
Exit;
end;
PrepareKerning(Font, Graphics, False);
try
P1 := PWideChar(Text);
while (P1^ <> #0) do
begin
Status := AddGlyphToGraphics(Graphics, P1^, Font, Origin, Format, Brush);
if Status <> Ok then
Break;
P2 := P1 + 1;
Origin.X := Origin.X + GetCellWidth(Word(P1^), Word(P2^),
DistanceFactor, KerningFactor);
Inc(P1);
end;
finally
UnprepareDC;
end;
Result := Status;
end;
function TGPKerningText.AddToGraphics(const Graphics: TGPGraphics;
const Text: WideString; const Font: TGPFont; Origin: TGPPoint;
const Format: TGPStringFormat; const Brush: TGPBrush;
const DistanceFactor: Single = 1; const KerningFactor: Single = 1): TStatus;
var
OriginF: TGPPointF;
begin
OriginF.X := Origin.X;
OriginF.Y := Origin.Y;
Result := AddToGraphics(Graphics, Text, Font, OriginF, Format, Brush,
DistanceFactor, KerningFactor);
end;
procedure TGPKerningText.AddToKerningList(const First, Second: Word;
Cell, Kerning: Integer);
var
Item: PKerning;
begin
GetMem(Item, SizeOf(TKerning));
Item^.Chars := First shl 16 + Second;
Item^.Cell := Cell;
Item^.Kerning := Kerning;
FKerningList.Add(Item);
end;
procedure TGPKerningText.Clear;
begin
UnprepareKerning;
UnprepareDC;
UnprepareFont;
FGPFont.Free;
FGPFont := nil;
end;
procedure TGPKerningText.ClearKerningList;
var
C: Integer;
begin
for C := 0 to FKerningList.Count - 1 do
FreeMem(FKerningList[C]);
FKerningList.Clear;
end;
constructor TGPKerningText.Create;
begin
inherited;
FKerningList := TList.Create;
end;
destructor TGPKerningText.Destroy;
begin
Clear;
FKerningList.Free;
inherited;
end;
function TGPKerningText.GetCellWidth(const First, Second: Word;
const DistanceFactor: Single = 1; const KerningFactor: Single = 1): Single;
var
GM: TGlyphMetrics;
Count: Cardinal;
Cell: Integer;
Kerning: Integer;
Mat: TMat2;
C: Integer;
begin
C := IndexOfKerning(First, Second);
if C <> -1 then
begin
Result := GetDistance(C, DistanceFactor, KerningFactor) * FFontSizeFactor;
Exit;
end;
FillChar(Mat, SizeOf(Mat), 0);
Mat.eM11.value := 1;
Mat.eM22.value := 1;
Count := GetGlyphOutlineW(FDC, First, GGO_METRICS, GM, 0, nil, Mat);
if (Count = GDI_ERROR) then
begin
Result := -1;
Exit;
end;
Cell := GM.gmCellIncX + GM.gmCellIncY;
Kerning := 0;
for C := 0 to FPairCount - 1 do
begin
if (FKerningPairs[C].wFirst = First) and
(FKerningPairs[C].wSecond = Second) then
begin
Kerning := FKerningPairs[C].iKernAmount;
Break;
end;
end;
AddToKerningList(First, Second, Cell, Kerning);
Result := (Cell * DistanceFactor + Kerning * KerningFactor) * FFontSizeFactor;
end;
function TGPKerningText.GetDistance(const Index: Integer;
const DistanceFactor, KerningFactor: Single): Single;
var
Kerning: PKerning;
begin
Kerning := PKerning(FKerningList[Index]);
Result := Kerning^.Cell * DistanceFactor + Kerning^.Kerning * KerningFactor;
end;
function TGPKerningText.IndexOfKerning(const First, Second: Word): Integer;
var
Chars: Cardinal;
begin
Chars := First shl 16 + Second;
for Result := 0 to FKerningList.Count - 1 do
if PKerning(FKerningList[Result])^.Chars = Chars then
Exit;
Result := -1;
end;
function TGPKerningText.MeasureText(const Text: WideString;
const Font: TGPFont; const DistanceFactor: Single = 1;
const KerningFactor: Single = 1): Single;
var
P1, P2: PWideChar;
begin
Result := 0;
if Text = '' then
Exit;
PrepareKerning(Font);
try
P1 := PWideChar(Text);
while (P1^ <> #0) do
begin
P2 := P1 + 1;
Result := Result + GetCellWidth(Word(P1^), Word(P2^), DistanceFactor,
KerningFactor);
Inc(P1);
end;
finally
UnprepareDC;
end;
end;
procedure TGPKerningText.Prepare(const Family: TGPFontFamily; Style: Integer;
const Size: Single; const Format: TGPStringFormat);
begin
FGPFont.Free;
FGPFont := TGPFont.Create(Family, Size, Style);
PrepareKerning(FGPFont);
end;
procedure TGPKerningText.PrepareDC;
begin
if (FDC <> 0) then
Exit;
FDC := GetDC(0);
FOldFont := SelectObject(FDC, FFont);
end;
procedure TGPKerningText.PrepareFont(const LF: TLogFontW);
begin
if (FFont <> 0) then
Exit;
FFont := CreateFontIndirectW(LF);
end;
procedure TGPKerningText.PrepareKerning(const Font: TGPFont;
const Graphics: TGPGraphics; WithFactor: Boolean);
var
LF: TLogFontW;
S: WideString;
DC: HDC;
Factor, Size: Single;
begin
Font.GetLogFontW(Graphics, LF);
Size := Font.GetSize;
if Font.GetUnit in [UnitWorld, UnitPixel] then
WithFactor := True;
if WithFactor then
begin
FFontSizeFactor := Size / 1000;
LF.lfHeight := -1000;
//LF.lfHeight := Round(Size * -1000)
end
else
begin
DC := Graphics.GetHDC;
Factor := -GetDeviceCaps(DC, LOGPIXELSY) / 72;
FFontSizeFactor := Size * Factor / 1000;
LF.lfHeight := 1000;
//LF.lfHeight := Round(Size * Factor * 1000);
Graphics.ReleaseHDC(DC);
end;
S := LF.lfFaceName;
if (S = FLastFont) and
(LF.lfWeight = FLastWeight) and (LF.lfItalic = FLastStyle) then
begin
PrepareDC;
Exit;
end else
UnprepareFont;
FLastFont := WideString(LF.lfFaceName);
FLastWeight := LF.lfWeight;
FLastStyle := LF.lfItalic;
PrepareFont(LF);
PrepareDC;
ClearKerningList;
FPairCount := GetKerningPairs(FDC, 0, PKerningPair(nil)^);
if (FPairCount > 0) then
begin
SetLength(FKerningPairs, FPairCount);
GetKerningPairs(FDC, FPairCount, FKerningPairs[0]);
end;
end;
procedure TGPKerningText.PrepareKerning(const Font: TGPFont);
var
G: TGPGraphics;
DC: HDC;
begin
DC := GetDC(0);
G := TGPGraphics.Create(DC);
PrepareKerning(Font, G, True);
G.Free;
ReleaseDC(0, DC);
end;
function TGPKerningText.AddToPath(const Path, UPath, LPath, OPath: TGPGraphicsPath;
const Text: WideString; const Family: TGPFontFamily; Style: Integer;
const Size: Single; TextOrigin: TTextOrigin; const DistanceFactor,
KerningFactor: Single): TStatus;
var
P1, P2: PWideChar;
Status: TStatus;
Font: TGPFont;
Width: Single;
SF: TGPStringFormat;
Start, Origin: TGPPointF;
i: Integer;
begin
Status := Ok;
if Text = '' then
begin
Result := Ok;
Exit;
end;
Style := Style and not FontStyleUnderline and not FontStyleStrikeout;
Font := TGPFont.Create(Family, Size, Style);
try
PrepareKerning(Font);
i := 0;
Start := MakePoint(TextOrigin.X[0] + TextOrigin.DX[0], TextOrigin.Y[0] + TextOrigin.DY[0]);
Origin := Start;
Width := MeasureText(Text, Font, DistanceFactor, KerningFactor);
AddDecoration(LPath, Start.X, Start.Y, Width, Font, 0.55);
SF := TGPStringFormat.Create(TGPStringFormat.GenericTypographic);
try
SF.SetFormatFlags(StringFormatFlagsMeasureTrailingSpaces);
try
P1 := PWideChar(Text);
while (P1^ <> #0) do
begin
Status := AddGlyphToPath(Path, P1^, Family, Style, Size, Origin, SF);
if Status <> Ok then
Break;
Inc(i);
if i <= High(TextOrigin.X) then
Origin.X := TextOrigin.X[i]
else
begin
P2 := P1 + 1;
Origin.X := Origin.X + GetCellWidth(Word(P1^), Word(P2^),
DistanceFactor, KerningFactor);
end;
if i <= High(TextOrigin.DX) then
Origin.X := Origin.X + TextOrigin.DX[i];
if i <= High(TextOrigin.Y) then
Origin.Y := TextOrigin.Y[i];
if i <= High(TextOrigin.DY) then
Origin.Y := Origin.Y + TextOrigin.DY[i];
Inc(P1);
end;
finally
UnprepareDC;
end;
finally
SF.Free;
end;
AddDecoration(UPath, Start.X, Start.Y, Width, Font, 0.95);
AddDecoration(OPath, Start.X, Start.Y, Width, Font, 0.05);
finally
Font.Free;
end;
Result := Status;
end;
procedure TGPKerningText.Unprepare;
begin
UnprepareDC;
FGPFont.Free;
FGPFont := nil;
end;
procedure TGPKerningText.UnprepareDC;
begin
if (FOldFont <> 0) and (FDC <> 0) then
SelectObject(FDC, FOldFont);
FoldFont := 0;
if FDC <> 0 then
ReleaseDC(0, FDC);
FDC := 0;
end;
procedure TGPKerningText.UnprepareFont;
begin
if FFont <> 0 then
DeleteObject(FFont);
FFont := 0;
end;
procedure TGPKerningText.UnprepareKerning;
begin
SetLength(FKerningPairs, 0);
FPairCount := 0;
ClearKerningList;
end;
initialization
FKerningText := nil;
finalization
FKerningText.Free;
end.