mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-05 10:37:58 +02:00

There are many fonts where СapHeight is either missing or zero. This fact cannot be hidden from the application, application must process it.
2638 lines
78 KiB
ObjectPascal
2638 lines
78 KiB
ObjectPascal
{
|
|
*****************************************************************************
|
|
This file is part of LazUtils.
|
|
|
|
See the file COPYING.modifiedLGPL.txt, included in this distribution,
|
|
for details about the license.
|
|
*****************************************************************************
|
|
|
|
to do :
|
|
|
|
- multiple font loading
|
|
- font face cache
|
|
- text rotation
|
|
}
|
|
unit EasyLazFreeType;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Math, Classes, SysUtils, fpimage, AVL_Tree,
|
|
// LazUtils
|
|
LazUTF8,
|
|
// FreeType
|
|
LazFreeType, TTRaster, TTTypes, TTObjs,
|
|
Types; // Note: Types must be after TTTypes for PByte.
|
|
|
|
type
|
|
TGlyphRenderQuality = (grqMonochrome, grqLowQuality, grqHighQuality);
|
|
ArrayOfSingle= array of single;
|
|
TCharPosition= record
|
|
x,width,
|
|
yTop,yBase,yBottom: single;
|
|
end;
|
|
ArrayOfCharPosition = array of TCharPosition;
|
|
TFreeTypeAlignment = (ftaLeft,ftaCenter,ftaRight,ftaJustify,ftaTop,ftaVerticalCenter,ftaBaseline,ftaBottom);
|
|
TFreeTypeAlignments = set of TFreeTypeAlignment;
|
|
|
|
TFreeTypeInformation = (ftiCopyrightNotice, ftiFamily, ftiStyle, ftiIdentifier, ftiFullName,
|
|
ftiVersionString, ftiPostscriptName, ftiTrademark, ftiManufacturer, ftiDesigner,
|
|
ftiVendorURL, ftiDesignerURL, ftiLicenseDescription, ftiLicenseInfoURL);
|
|
|
|
TFreeTypeStyle = (ftsBold, ftsItalic);
|
|
TFreeTypeStyles = set of TFreeTypeStyle;
|
|
TFreeTypeWordBreakHandler = procedure(var ABefore, AAfter: string) of object;
|
|
|
|
const
|
|
FreeTypeInformationStr : array[TFreeTypeInformation] of string =
|
|
('Copyright notice', 'Family', 'Style', 'Identifier', 'Full name',
|
|
'Version string', 'Postscript name', 'Trademark', 'Manufacturer', 'Designer',
|
|
'Vendor URL', 'Designer URL', 'License description', 'License info URL');
|
|
|
|
type
|
|
TFreeTypeGlyph = class;
|
|
TFreeTypeFont = class;
|
|
TFreeTypeKerning = record
|
|
Kerning, Minimum: TPointF;
|
|
Found: boolean;
|
|
end;
|
|
|
|
EFreeType = class(Exception);
|
|
|
|
TFontCollectionItemDestroyProc = procedure() of object;
|
|
TFontCollectionItemDestroyListener = record
|
|
TargetObject: TObject;
|
|
NotifyProc: TFontCollectionItemDestroyProc;
|
|
end;
|
|
|
|
function FontCollectionItemDestroyListener(ATargetObject: TObject; ANotifyProc: TFontCollectionItemDestroyProc): TFontCollectionItemDestroyListener;
|
|
|
|
type
|
|
ArrayOfFontCollectionItemDestroyListener = array of TFontCollectionItemDestroyListener;
|
|
TCustomFamilyCollectionItem = class;
|
|
|
|
{ TCustomFontCollectionItem }
|
|
|
|
TCustomFontCollectionItem = class
|
|
protected
|
|
FFamily: TCustomFamilyCollectionItem;
|
|
function GetBold: boolean; virtual; abstract;
|
|
function GetInformation(AIndex: TFreeTypeInformation): string; virtual; abstract;
|
|
function GetItalic: boolean; virtual; abstract;
|
|
function GetStyleCount: integer; virtual; abstract;
|
|
function GetStyles: string; virtual; abstract;
|
|
function GetFilename: string; virtual; abstract;
|
|
function GetVersionNumber: string; virtual; abstract;
|
|
function GetStyle(AIndex: integer): string; virtual; abstract;
|
|
procedure NotifyDestroy; virtual; abstract;
|
|
public
|
|
function HasStyle(AStyle: string): boolean; virtual; abstract;
|
|
function CreateFont: TFreeTypeFont; virtual; abstract;
|
|
function QueryFace(AListener: TFontCollectionItemDestroyListener): TT_Face; virtual; abstract;
|
|
procedure ReleaseFace(AListener: TFontCollectionItemDestroyListener); virtual; abstract;
|
|
|
|
property Styles: string read GetStyles;
|
|
property Italic: boolean read GetItalic;
|
|
property Bold: boolean read GetBold;
|
|
property Filename: string read GetFilename;
|
|
property Information[AIndex: TFreeTypeInformation]: string read GetInformation;
|
|
property VersionNumber: string read GetVersionNumber;
|
|
property Style[AIndex: integer]: string read GetStyle;
|
|
property StyleCount: integer read GetStyleCount;
|
|
property Family: TCustomFamilyCollectionItem read FFamily write FFamily;
|
|
end;
|
|
|
|
IFreeTypeFontEnumerator = interface
|
|
function MoveNext: boolean;
|
|
function GetCurrent: TCustomFontCollectionItem;
|
|
property Current: TCustomFontCollectionItem read GetCurrent;
|
|
end;
|
|
|
|
{ TCustomFamilyCollectionItem }
|
|
|
|
TCustomFamilyCollectionItem = class
|
|
protected
|
|
function GetFontByIndex(AIndex: integer): TCustomFontCollectionItem; virtual; abstract;
|
|
function GetStyle(AIndex: integer): string; virtual; abstract;
|
|
function GetStyles: string; virtual; abstract;
|
|
function GetFamilyName: string; virtual; abstract;
|
|
function GetFontCount: integer; virtual; abstract;
|
|
function GetStyleCount: integer; virtual; abstract;
|
|
public
|
|
function GetFont(const AStyles: array of string; NeedAllStyles: boolean = false; NoMoreStyle: boolean = false): TCustomFontCollectionItem; virtual; abstract; overload;
|
|
function GetFont(AStyle: string; NeedAllStyles: boolean = false; NoMoreStyle: boolean = false): TCustomFontCollectionItem; virtual; abstract; overload;
|
|
function GetFontIndex(const AStyles: array of string; NeedAllStyles: boolean = false; NoMoreStyle: boolean = false): integer; virtual; abstract; overload;
|
|
function GetFontIndex(AStyle: string; NeedAllStyles: boolean = false; NoMoreStyle: boolean = false): integer; virtual; abstract; overload;
|
|
function HasStyle(AName: string): boolean; virtual; abstract;
|
|
property FamilyName: string read GetFamilyName;
|
|
property Font[AIndex: integer]: TCustomFontCollectionItem read GetFontByIndex;
|
|
property FontCount: integer read GetFontCount;
|
|
property Style[AIndex: integer]: string read GetStyle;
|
|
property StyleCount: integer read GetStyleCount;
|
|
property Styles: string read GetStyles;
|
|
end;
|
|
|
|
IFreeTypeFamilyEnumerator = interface
|
|
function MoveNext: boolean;
|
|
function GetCurrent: TCustomFamilyCollectionItem;
|
|
property Current: TCustomFamilyCollectionItem read GetCurrent;
|
|
end;
|
|
|
|
{ TCustomFreeTypeFontCollection }
|
|
|
|
TCustomFreeTypeFontCollection = class
|
|
protected
|
|
function GetFont(AFileName: string): TCustomFontCollectionItem; virtual; abstract;
|
|
function GetFamily(AName: string): TCustomFamilyCollectionItem; virtual; abstract;
|
|
function GetFamilyCount: integer; virtual; abstract;
|
|
function GetFontCount: integer; virtual; abstract;
|
|
public
|
|
constructor Create; virtual; abstract;
|
|
procedure Clear; virtual; abstract;
|
|
procedure BeginUpdate; virtual; abstract;
|
|
procedure AddFolder(AFolder: string; AIncludeSubdirs: Boolean = false); virtual; abstract;
|
|
procedure RemoveFolder(AFolder: string); virtual; abstract;
|
|
function AddFile(AFilename: string): TCustomFontCollectionItem; virtual; abstract;
|
|
function RemoveFile(AFilename: string): boolean; virtual; abstract;
|
|
function AddStream(AStream: TStream; AOwned: boolean): boolean; virtual; abstract;
|
|
procedure EndUpdate; virtual; abstract;
|
|
function FontFileEnumerator: IFreeTypeFontEnumerator; virtual; abstract;
|
|
function FamilyEnumerator: IFreeTypeFamilyEnumerator; virtual; abstract;
|
|
property FontFileCount: integer read GetFontCount;
|
|
property FontFile[AFileName: string]: TCustomFontCollectionItem read GetFont;
|
|
property FamilyCount: integer read GetFamilyCount;
|
|
property Family[AName: string]: TCustomFamilyCollectionItem read GetFamily;
|
|
end;
|
|
|
|
{***************************** Rendering classes *********************************}
|
|
|
|
TOnRenderTextHandler = procedure(AText: string; x,y: single) of object;
|
|
|
|
{ TFreeTypeRenderableFont }
|
|
|
|
TFreeTypeRenderableFont = class
|
|
protected
|
|
FWordBreakHandler: TFreeTypeWordBreakHandler;
|
|
FOnRenderText: TOnRenderTextHandler;
|
|
function GetClearType: boolean; virtual; abstract;
|
|
procedure SetClearType(const AValue: boolean); virtual; abstract;
|
|
function GetLineFullHeight: single; virtual; abstract;
|
|
function GetAscent: single; virtual; abstract;
|
|
function GetDescent: single; virtual; abstract;
|
|
function GetLineSpacing: single; virtual; abstract;
|
|
procedure DefaultWordBreakHandler(var ABefore, AAfter: string);
|
|
function GetHinted: boolean; virtual; abstract;
|
|
procedure SetHinted(const AValue: boolean); virtual; abstract;
|
|
public
|
|
UnderlineDecoration,StrikeOutDecoration: boolean;
|
|
Orientation: integer;
|
|
function TextWidth(AText: string): single; virtual; abstract;
|
|
function TextHeight(AText: string): single; virtual; abstract;
|
|
function CharWidthFromUnicode(AUnicode: integer): single; virtual; abstract;
|
|
procedure SplitText(var AText: string; AMaxWidth: single; out ARemains: string);
|
|
procedure GetTextSize(AText: string; out w,h: single); virtual;
|
|
procedure RenderText(AText: string; x,y: single; ARect: TRect; OnRender : TDirectRenderingFunction); virtual; abstract;
|
|
property ClearType: boolean read GetClearType write SetClearType;
|
|
property Ascent: single read GetAscent;
|
|
property Descent: single read GetDescent;
|
|
property LineSpacing: single read GetLineSpacing;
|
|
property LineFullHeight: single read GetLineFullHeight;
|
|
property Hinted: boolean read GetHinted write SetHinted;
|
|
property OnWordBreak: TFreeTypeWordBreakHandler read FWordBreakHandler write FWordBreakHandler;
|
|
property OnRenderText: TOnRenderTextHandler read FOnRenderText write FOnRenderText;
|
|
end;
|
|
|
|
{ TFreeTypeDrawer }
|
|
|
|
TFreeTypeDrawer = class
|
|
procedure DrawText(AText: string; AFont: TFreeTypeRenderableFont; x,y: single; AColor: TFPColor; AOpacity: Byte); virtual; overload;
|
|
procedure DrawText(AText: string; AFont: TFreeTypeRenderableFont; x,y: single; AColor: TFPColor; AOpacity: Byte; AAlign: TFreeTypeAlignments); virtual; overload;
|
|
procedure DrawText(AText: string; AFont: TFreeTypeRenderableFont; x,y: single; AColor: TFPColor); virtual; abstract; overload;
|
|
procedure DrawText(AText: string; AFont: TFreeTypeRenderableFont; x,y: single; AColor: TFPColor; AAlign: TFreeTypeAlignments); virtual; overload;
|
|
procedure DrawTextWordBreak(AText: string; AFont: TFreeTypeRenderableFont; x, y, AMaxWidth: Single; AColor: TFPColor; AAlign: TFreeTypeAlignments);
|
|
procedure DrawTextRect(AText: string; AFont: TFreeTypeRenderableFont; X1,Y1,X2,Y2: Single; AColor: TFPColor; AAlign: TFreeTypeAlignments);
|
|
procedure DrawGlyph(AGlyph: integer; AFont: TFreeTypeRenderableFont; x,y: single; AColor: TFPColor); virtual; abstract; overload;
|
|
procedure DrawGlyph(AGlyph: integer; AFont: TFreeTypeRenderableFont; x,y: single; AColor: TFPColor; AAlign: TFreeTypeAlignments); virtual; overload;
|
|
end;
|
|
|
|
{********************************* Font implementation **********************************}
|
|
|
|
{ TFreeTypeFont }
|
|
|
|
TFreeTypeFont = class(TFreeTypeRenderableFont)
|
|
private
|
|
FName: String;
|
|
FFaceChanged: boolean;
|
|
FDPI: integer;
|
|
FStream: TStream;
|
|
FOwnedStream: boolean;
|
|
FPointSize: single;
|
|
FHinted: boolean;
|
|
FKerningEnabled, FKerningFallbackEnabled: boolean;
|
|
FStyleStr: string;
|
|
FWidthFactor: single;
|
|
FClearType: boolean;
|
|
FNamesArray: array of string;
|
|
FCollection: TCustomFreeTypeFontCollection;
|
|
function FindGlyphNode(Index: Integer): TAvlTreeNode;
|
|
function GetCharIndex(AUnicodeChar: integer): integer;
|
|
function GetDPI: integer;
|
|
function GetFamily: string;
|
|
function GetFreeTypeStyles: TFreeTypeStyles;
|
|
function GetGlyph(Index: integer): TFreeTypeGlyph;
|
|
function GetGlyphCount: integer;
|
|
function GetInformation(AIndex: TFreeTypeInformation): string;
|
|
function GetGlyphKerning(AGlyphLeft, AGlyphRight: integer): TFreeTypeKerning;
|
|
function GetCharKerning(AUnicodeCharLeft, AUnicodeCharRight: integer): TFreeTypeKerning;
|
|
function GetPixelSize: single;
|
|
function GetVersionNumber: string;
|
|
procedure SetDPI(const AValue: integer);
|
|
procedure SetFreeTypeStyles(AValue: TFreeTypeStyles);
|
|
procedure SetLineFullHeight(AValue: single);
|
|
procedure SetStyleAsString(AValue: string);
|
|
procedure LoadFace;
|
|
procedure SetName(const AValue: String);
|
|
procedure DiscardFace;
|
|
procedure DiscardInstance;
|
|
procedure DiscardStream;
|
|
procedure SetPixelSize(const AValue: single);
|
|
procedure SetPointSize(AValue: single);
|
|
function LoadGlyphInto(_glyph : TT_Glyph;
|
|
glyph_index : Word): boolean;
|
|
procedure SetWidthFactor(const AValue: single);
|
|
procedure UpdateInstance;
|
|
procedure UpdateSizeInPoints;
|
|
procedure UpdateMetrics;
|
|
procedure UpdateCharmap;
|
|
procedure RenderTextDecoration(AText: string; x,y: single; ARect: TRect; OnRender : TDirectRenderingFunction);
|
|
procedure FillRect(ARect: TRect; OnRender : TDirectRenderingFunction);
|
|
protected
|
|
FFace: TT_Face;
|
|
FFaceItem: TCustomFontCollectionItem;
|
|
FFaceLoaded: boolean;
|
|
FInstance: TT_Instance;
|
|
FInstanceCreated : boolean;
|
|
FGlyphTable: TAvlTree;
|
|
FCharMap: TT_CharMap;
|
|
FCharmapOk, FCharmapSymbol: boolean;
|
|
FAscentValue, FDescentValue, FLineGapValue, FLargeLineGapValue, FCapHeight: single;
|
|
FUnitsPerEM: TT_UShort;
|
|
procedure FaceChanged;
|
|
function GetClearType: boolean; override;
|
|
procedure SetClearType(const AValue: boolean); override;
|
|
function GetLineFullHeight: single; override;
|
|
function GetAscent: single; override;
|
|
function GetDescent: single; override;
|
|
function GetLineSpacing: single; override;
|
|
function GetCapHeight: single;
|
|
procedure SetHinted(const AValue: boolean); override;
|
|
function GetHinted: boolean; override;
|
|
procedure OnDestroyFontItem;
|
|
procedure FetchNames;
|
|
function GetCollection: TCustomFreeTypeFontCollection;
|
|
function CheckFace: boolean;
|
|
public
|
|
Quality : TGlyphRenderQuality;
|
|
SmallLinePadding: boolean;
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
procedure AccessFromStream(AStream: TStream; AStreamOwner: boolean);
|
|
procedure RenderText(AText: string; x,y: single; ARect: TRect; OnRender : TDirectRenderingFunction); override;
|
|
procedure RenderGlyph(AGlyph: Integer; x,y: single; ARect: TRect; OnRender : TDirectRenderingFunction);
|
|
procedure SetNameAndStyle(AName: string; AStyle: string); overload;
|
|
procedure SetNameAndStyle(AName: string; AStyle: TFreeTypeStyles); overload;
|
|
function TextWidth(AText: string): single; override;
|
|
function TextHeight(AText: string): single; override;
|
|
function CharWidthFromUnicode(AUnicodeChar: integer): single; override;
|
|
function CharWidthFromGlyph(AGlyph: integer): single;
|
|
function CharsWidth(AText: string): ArrayOfSingle;
|
|
function CharsPosition(AText: string): ArrayOfCharPosition; overload;
|
|
function CharsPosition(AText: string; AAlign: TFreeTypeAlignments): ArrayOfCharPosition; overload;
|
|
function CheckInstance: boolean;
|
|
property Name: String read FName write SetName;
|
|
property DPI: integer read GetDPI write SetDPI;
|
|
property SizeInPoints: single read FPointSize write SetPointSize;
|
|
property SizeInPixels: single read GetPixelSize write SetPixelSize;
|
|
property CapHeight: single read GetCapHeight;
|
|
property Glyph[Index: integer]: TFreeTypeGlyph read GetGlyph;
|
|
property GlyphCount: integer read GetGlyphCount;
|
|
property CharKerning[AUnicodeCharLeft, AUnicodeCharRight: integer]: TFreeTypeKerning read GetCharKerning;
|
|
property GlyphKerning[AGlyphLeft, AGlyphRight: integer]: TFreeTypeKerning read GetGlyphKerning;
|
|
property CharIndex[AUnicodeChar: integer]: integer read GetCharIndex;
|
|
property Hinted: boolean read FHinted write SetHinted;
|
|
{ Kerning brings closer certain letters that fit together }
|
|
property KerningEnabled: boolean read FKerningEnabled write FKerningEnabled;
|
|
{ When enabled, if the kerning is not found between two letters, alternate codes are tried }
|
|
property KerningFallbackEnabled: boolean read FKerningFallbackEnabled write FKerningFallbackEnabled;
|
|
property WidthFactor: single read FWidthFactor write SetWidthFactor;
|
|
property LineFullHeight: single read GetLineFullHeight write SetLineFullHeight;
|
|
property Information[AIndex: TFreeTypeInformation]: string read GetInformation;
|
|
property VersionNumber: string read GetVersionNumber;
|
|
property Family: string read GetFamily;
|
|
property Collection: TCustomFreeTypeFontCollection read GetCollection write FCollection;
|
|
property StyleAsString: string read FStyleStr write SetStyleAsString;
|
|
property Style: TFreeTypeStyles read GetFreeTypeStyles write SetFreeTypeStyles;
|
|
end;
|
|
|
|
{ TFreeTypeGlyph }
|
|
|
|
TFreeTypeGlyph = class
|
|
private
|
|
FLoaded: boolean;
|
|
FGlyphData: TT_Glyph;
|
|
FIndex: integer;
|
|
FOrientation: Integer;
|
|
function GetAdvance: single;
|
|
function GetBounds: TRect;
|
|
function GetBoundsWithOffset(x, y: single): TRect;
|
|
{%H-}constructor create;
|
|
public
|
|
constructor Create(AFont: TFreeTypeFont; AIndex: integer);
|
|
function RenderDirectly(x,y: single; Rect: TRect; OnRender : TDirectRenderingFunction; quality : TGlyphRenderQuality; ClearType: boolean = false): boolean;
|
|
function RenderDirectly(ARasterizer: TFreeTypeRasterizer; x,y: single; Rect: TRect; OnRender : TDirectRenderingFunction; quality : TGlyphRenderQuality; ClearType: boolean = false): boolean;
|
|
function Clone(AOrientation:Integer): TFreeTypeGlyph;
|
|
destructor Destroy; override;
|
|
property Loaded: boolean read FLoaded;
|
|
property Data: TT_Glyph read FGlyphData;
|
|
property Index: integer read FIndex;
|
|
property Bounds: TRect read GetBounds;
|
|
property BoundsWithOffset[x,y: single]: TRect read GetBoundsWithOffset;
|
|
property Advance: single read GetAdvance;
|
|
end;
|
|
|
|
{ TFreeTypeRasterMap }
|
|
|
|
TFreeTypeRasterMap = class
|
|
protected
|
|
map: TT_Raster_Map;
|
|
FRasterizer: TFreeTypeRasterizer;
|
|
function GetHeight: integer; virtual;
|
|
function GetWidth: integer; virtual;
|
|
function GetScanLine(y: integer): pointer;
|
|
procedure Init(AWidth,AHeight: integer); virtual; abstract;
|
|
public
|
|
constructor Create(AWidth,AHeight: integer); virtual;
|
|
constructor Create(ARasterizer: TFreeTypeRasterizer; AWidth,AHeight: integer); virtual;
|
|
procedure Clear;
|
|
procedure Fill;
|
|
function RenderGlyph(glyph : TFreeTypeGlyph; x,y: single) : boolean; virtual; abstract;
|
|
procedure ScanMoveTo(x,y: integer); virtual; abstract;
|
|
destructor Destroy; override;
|
|
|
|
property Width: integer read GetWidth;
|
|
property Height: integer read GetHeight;
|
|
property ScanLine[y: integer]: pointer read GetScanLine;
|
|
end;
|
|
|
|
{ TFreeTypeMonochromeMap }
|
|
|
|
TFreeTypeMonochromeMap = class(TFreeTypeRasterMap)
|
|
private
|
|
ScanPtrStart,ScanPtrCur: pbyte;
|
|
ScanBit: byte;
|
|
ScanX: integer;
|
|
function GetPixelsInHorizlineNoBoundsChecking(x,y,x2: integer) : integer; inline;
|
|
protected
|
|
procedure Init(AWidth,AHeight: integer); override;
|
|
public
|
|
function RenderGlyph(glyph : TFreeTypeGlyph; x,y: single) : boolean; override;
|
|
procedure ScanMoveTo(x,y: integer); override;
|
|
function ScanNextPixel: boolean;
|
|
function GetPixel(x,y: integer): boolean;
|
|
procedure SetPixel(x,y: integer; value: boolean);
|
|
function GetPixelsInRect(x,y,x2,y2: integer): integer;
|
|
function GetPixelsInHorizline(x,y,x2: integer): integer;
|
|
procedure TogglePixel(x,y: integer);
|
|
end;
|
|
|
|
{ TFreeTypeGrayscaleMap }
|
|
|
|
TFreeTypeGrayscaleMap = class(TFreeTypeRasterMap)
|
|
private
|
|
ScanPtrStart: pbyte;
|
|
ScanX: integer;
|
|
protected
|
|
procedure Init(AWidth, AHeight: integer); override;
|
|
public
|
|
RenderQuality: TGlyphRenderQuality;
|
|
function RenderGlyph(glyph : TFreeTypeGlyph; x,y: single) : boolean; override;
|
|
procedure ScanMoveTo(x,y: integer); override;
|
|
function ScanNextPixel: byte;
|
|
function GetPixel(x,y: integer): byte;
|
|
procedure SetPixel(x,y: integer; value: byte);
|
|
procedure XorPixel(x,y: integer; value: byte);
|
|
end;
|
|
|
|
var
|
|
FontCollection: TCustomFreeTypeFontCollection;
|
|
|
|
function StylesToArray(AStyles: string): TStringArray;
|
|
|
|
const FreeTypeMinPointSize = 1;
|
|
|
|
implementation
|
|
|
|
const
|
|
TT_PLATFORM_APPLE_UNICODE = 0;
|
|
//TT_PLATFORM_MACINTOSH = 1;
|
|
TT_PLATFORM_ISO = 2; // deprecated
|
|
TT_PLATFORM_MICROSOFT = 3;
|
|
//TT_PLATFORM_CUSTOM = 4;
|
|
//TT_PLATFORM_ADOBE = 7; // artificial
|
|
|
|
function FontCollectionItemDestroyListener(ATargetObject: TObject;
|
|
ANotifyProc: TFontCollectionItemDestroyProc
|
|
): TFontCollectionItemDestroyListener;
|
|
begin
|
|
result.TargetObject := ATargetObject;
|
|
result.NotifyProc := ANotifyProc;
|
|
end;
|
|
|
|
function StylesToArray(AStyles: string): TStringArray;
|
|
var
|
|
StartIndex, EndIndex: integer;
|
|
Count: integer;
|
|
|
|
procedure AddStyle(AName: string);
|
|
begin
|
|
if (AName = 'Normal') or (AName = 'Regular') or (AName = 'Roman') or (AName = 'Plain') or (AName = 'Book') then exit;
|
|
if Count = length(result) then
|
|
setlength(result, length(result)+4);
|
|
result[Count] := AName;
|
|
inc(Count);
|
|
end;
|
|
|
|
begin
|
|
Count := 0;
|
|
result := nil;
|
|
StartIndex := 1;
|
|
while StartIndex <= length(AStyles) do
|
|
begin
|
|
while (StartIndex < length(AStyles)) and (AStyles[StartIndex] = ' ') do inc(StartIndex);
|
|
if AStyles[StartIndex] <> ' ' then
|
|
begin
|
|
EndIndex := StartIndex;
|
|
while (EndIndex < length(AStyles)) and (AStyles[EndIndex+1] <> ' ') do inc(EndIndex);
|
|
AddStyle(copy(AStyles, StartIndex, EndIndex-StartIndex+1));
|
|
StartIndex := EndIndex+1;
|
|
end;
|
|
end;
|
|
setlength(result,Count);
|
|
end;
|
|
|
|
var
|
|
BitCountTable: packed array[0..255] of byte;
|
|
RegularGray5: TT_Gray_Palette;
|
|
FreeTypeInitialized,FreeTypeCannotInitialize : boolean;
|
|
|
|
procedure EnsureFreeTypeInitialized;
|
|
begin
|
|
if not FreeTypeInitialized and not FreeTypeCannotInitialize then
|
|
begin
|
|
FreeTypeInitialized := (TT_Init_FreeType = TT_Err_Ok);
|
|
FreeTypeCannotInitialize := not FreeTypeInitialized;
|
|
end;
|
|
if FreeTypeCannotInitialize then
|
|
raise EFreeType.Create('FreeType cannot be initialized');
|
|
end;
|
|
|
|
function GlyphTableOnCompare(Item1, Item2: Pointer): Integer;
|
|
var
|
|
G1: TFreeTypeGlyph absolute Item1;
|
|
G2: TFreeTypeGlyph absolute Item2;
|
|
begin
|
|
if G1.Index > G2.Index then
|
|
Result := 1
|
|
else if G1.Index < G2.Index then
|
|
Result := -1
|
|
else
|
|
Result := 0;
|
|
end;
|
|
|
|
{ TFreeTypeRenderableFont }
|
|
|
|
procedure TFreeTypeRenderableFont.DefaultWordBreakHandler(var ABefore, AAfter: string);
|
|
var p: integer;
|
|
begin
|
|
if (AAfter <> '') and (ABefore <> '') and (AAfter[1]<> ' ') and (ABefore[length(ABefore)] <> ' ') then
|
|
begin
|
|
p := length(ABefore);
|
|
while (p > 1) and (ABefore[p-1] <> ' ') do dec(p);
|
|
if p > 1 then //can put the word after
|
|
begin
|
|
AAfter := copy(ABefore,p,length(ABefore)-p+1)+AAfter;
|
|
SetLength(ABefore, p-1);
|
|
end else
|
|
begin //cannot put the word after, so before
|
|
|
|
end;
|
|
end;
|
|
while (ABefore <> '') and (ABefore[length(ABefore)] =' ') do
|
|
SetLength(ABefore, length(ABefore)-1);
|
|
while (AAfter <> '') and (AAfter[1] =' ') do
|
|
Delete(AAfter,1,1);
|
|
end;
|
|
|
|
procedure TFreeTypeRenderableFont.SplitText(var AText: string;
|
|
AMaxWidth: single; out ARemains: string);
|
|
var
|
|
pstr: pchar;
|
|
left,charlen: integer;
|
|
totalWidth: single;
|
|
firstChar: boolean;
|
|
glyphWidth: single;
|
|
glyphCode: cardinal;
|
|
|
|
procedure WordBreak(ADropCount: Integer = 0);
|
|
begin
|
|
ARemains:= copy(AText, length(AText) - left + 1 + ADropCount, left);
|
|
SetLength(AText, length(AText) - left);
|
|
end;
|
|
|
|
begin
|
|
ARemains := '';
|
|
if AText = '' then
|
|
exit;
|
|
totalWidth := 0;
|
|
pstr := @AText[1];
|
|
left := length(AText);
|
|
firstChar := true;
|
|
while left > 0 do
|
|
begin
|
|
if pstr[0] in [#13, #10] then
|
|
begin
|
|
if (left > 1) and ([pstr[0], pstr[1]] = [#13, #10]) then
|
|
WordBreak(2)
|
|
else
|
|
WordBreak(1);
|
|
exit;
|
|
end;
|
|
|
|
charlen := UTF8CodepointSize(pstr);
|
|
glyphCode := UTF8CodepointToUnicode(pstr, charlen);
|
|
inc(pstr,charlen);
|
|
|
|
glyphWidth := CharWidthFromUnicode(glyphCode);
|
|
if glyphWidth <> 0 then
|
|
begin
|
|
totalWidth += glyphWidth;
|
|
if (totalWidth > AMaxWidth) and not firstChar then
|
|
begin
|
|
WordBreak;
|
|
if Assigned(FWordBreakHandler) then
|
|
FWordBreakHandler(AText,ARemains)
|
|
else
|
|
DefaultWordBreakHandler(AText,ARemains);
|
|
exit;
|
|
end;
|
|
end;
|
|
|
|
dec(left,charlen);
|
|
firstChar := false;
|
|
end;
|
|
ARemains := ''; //no split
|
|
end;
|
|
|
|
procedure TFreeTypeRenderableFont.GetTextSize(AText: string; out w, h: single);
|
|
begin
|
|
w := TextWidth(AText);
|
|
h := TextHeight(AText);
|
|
end;
|
|
|
|
{ TFreeTypeDrawer }
|
|
|
|
procedure TFreeTypeDrawer.DrawText(AText: string;
|
|
AFont: TFreeTypeRenderableFont; x, y: single; AColor: TFPColor; AOpacity: Byte);
|
|
var col: TFPColor;
|
|
begin
|
|
col := AColor;
|
|
col.alpha := col.alpha*AOpacity div 255;
|
|
DrawText(AText, AFont, x,y, col, []);
|
|
end;
|
|
|
|
procedure TFreeTypeDrawer.DrawText(AText: string;
|
|
AFont: TFreeTypeRenderableFont; x, y: single; AColor: TFPColor; AOpacity: Byte; AAlign: TFreeTypeAlignments);
|
|
var col: TFPColor;
|
|
begin
|
|
col := AColor;
|
|
col.alpha := col.alpha*AOpacity div 255;
|
|
DrawText(AText, AFont, x,y, col, AAlign);
|
|
end;
|
|
|
|
procedure TFreeTypeDrawer.DrawText(AText: string;
|
|
AFont: TFreeTypeRenderableFont; x, y: single; AColor: TFPColor; AAlign: TFreeTypeAlignments);
|
|
var idx : integer;
|
|
delta: single;
|
|
begin
|
|
if not (ftaBaseline in AAlign) then
|
|
begin
|
|
if ftaTop in AAlign then
|
|
y += AFont.Ascent else
|
|
if ftaBottom in AAlign then
|
|
y += AFont.Ascent - AFont.TextHeight(AText) else
|
|
if ftaVerticalCenter in AAlign then
|
|
y += AFont.Ascent - AFont.TextHeight(AText)*0.5;
|
|
end;
|
|
AAlign -= [ftaTop,ftaBaseline,ftaBottom,ftaVerticalCenter];
|
|
|
|
idx := pos(LineEnding, AText);
|
|
while idx <> 0 do
|
|
begin
|
|
DrawText(copy(AText,1,idx-1), AFont, x,y, AColor, AAlign);
|
|
delete(AText,1,idx+length(LineEnding)-1);
|
|
idx := pos(LineEnding, AText);
|
|
y += AFont.LineFullHeight;
|
|
end;
|
|
|
|
if not (ftaLeft in AAlign) then
|
|
begin
|
|
delta := 0;
|
|
if ftaCenter in AAlign then
|
|
delta := -AFont.TextWidth(AText)/2 else
|
|
if ftaRight in AAlign then
|
|
delta := -AFont.TextWidth(AText);
|
|
if AFont.Hinted then delta := round(delta);
|
|
x += delta;
|
|
end;
|
|
DrawText(AText, AFont, x,y, AColor);
|
|
end;
|
|
|
|
procedure TFreeTypeDrawer.DrawTextWordBreak(AText: string;
|
|
AFont: TFreeTypeRenderableFont; x, y, AMaxWidth: Single;
|
|
AColor: TFPColor; AAlign: TFreeTypeAlignments);
|
|
var ARemains: string;
|
|
stepX,stepY: single;
|
|
lines: TStringList;
|
|
i: integer;
|
|
lineShift: single;
|
|
lineAlignment: TFreeTypeAlignments;
|
|
begin
|
|
if (AText = '') or (AMaxWidth <= 0) then exit;
|
|
|
|
stepX := 0;
|
|
stepY := AFont.LineFullHeight;
|
|
|
|
AAlign -= [ftaBaseline]; //ignored
|
|
if AAlign * [ftaTop,ftaVerticalCenter,ftaBottom] = [] then AAlign += [ftaTop]; //top by default
|
|
lineAlignment := AAlign * [ftaLeft,ftaCenter,ftaRight] + [ftaVerticalCenter];
|
|
|
|
if ftaTop in AAlign then
|
|
begin
|
|
lineShift := 0.5;
|
|
X += stepX*lineShift;
|
|
Y += stepY*lineShift;
|
|
repeat
|
|
AFont.SplitText(AText, AMaxWidth, ARemains);
|
|
DrawText(AText,AFont,X,Y,AColor,lineAlignment);
|
|
AText := ARemains;
|
|
X+= stepX;
|
|
Y+= stepY;
|
|
until ARemains = '';
|
|
end else
|
|
begin
|
|
lines := TStringList.Create;
|
|
repeat
|
|
AFont.SplitText(AText, AMaxWidth, ARemains);
|
|
lines.Add(AText);
|
|
AText := ARemains;
|
|
until ARemains = '';
|
|
if ftaVerticalCenter in AAlign then lineShift := lines.Count/2-0.5
|
|
else if ftaBottom in AAlign then lineShift := lines.Count-0.5
|
|
else lineShift := -0.5;
|
|
|
|
X -= stepX*lineShift;
|
|
Y -= stepY*lineShift;
|
|
for i := 0 to lines.Count-1 do
|
|
begin
|
|
DrawText(lines[i],AFont,X,Y,AColor,lineAlignment);
|
|
X+= stepX;
|
|
Y+= stepY;
|
|
end;
|
|
lines.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TFreeTypeDrawer.DrawTextRect(AText: string;
|
|
AFont: TFreeTypeRenderableFont; X1, Y1, X2, Y2: Single; AColor: TFPColor;
|
|
AAlign: TFreeTypeAlignments);
|
|
var X,Y: single;
|
|
begin
|
|
if X2 <= X1 then exit;
|
|
if ftaVerticalCenter in AAlign then Y := (Y1+Y2)/2 else
|
|
if ftaBottom in AAlign then Y := Y2 else
|
|
Y := Y1;
|
|
if ftaCenter in AAlign then X := (X1+X2)/2 else
|
|
if ftaRight in AAlign then X := X2 else
|
|
X := X1;
|
|
DrawTextWordBreak(AText,AFont,X,Y,X2-X1,AColor,AAlign);
|
|
end;
|
|
|
|
procedure TFreeTypeDrawer.DrawGlyph(AGlyph: integer;
|
|
AFont: TFreeTypeRenderableFont; x, y: single; AColor: TFPColor;
|
|
AAlign: TFreeTypeAlignments);
|
|
var f: TFreeTypeFont;
|
|
begin
|
|
if not (AFont is TFreeTypeFont) then exit;
|
|
f := TFreeTypeFont(Afont);
|
|
|
|
if ftaTop in AAlign then
|
|
y += AFont.Ascent
|
|
else if ftaVerticalCenter in AALign then
|
|
y += AFont.Ascent - AFont.LineFullHeight*0.5
|
|
else if ftaBottom in AAlign then
|
|
y += AFont.Ascent - AFont.LineFullHeight;
|
|
|
|
if ftaCenter in AAlign then
|
|
x -= f.CharWidthFromGlyph(AGlyph)*0.5
|
|
else if ftaRight in AAlign then
|
|
x -= f.CharWidthFromGlyph(AGlyph);
|
|
|
|
DrawGlyph(AGlyph, AFont, x,y, AColor);
|
|
end;
|
|
|
|
{ TFreeTypeGlyph }
|
|
|
|
function TFreeTypeGlyph.GetBounds: TRect;
|
|
begin
|
|
result := GetBoundsWithOffset(0, 0);
|
|
end;
|
|
|
|
function TFreeTypeGlyph.GetAdvance: single;
|
|
var
|
|
metrics: TT_Glyph_Metrics;
|
|
begin
|
|
if TT_Get_Glyph_Metrics(FGlyphData, metrics) = TT_Err_Ok then
|
|
result := metrics.advance/64
|
|
else
|
|
result := 0;
|
|
end;
|
|
|
|
function TFreeTypeGlyph.GetBoundsWithOffset(x, y: single): TRect;
|
|
var
|
|
metrics: TT_Glyph_Metrics;
|
|
outline: TT_Outline;
|
|
bbox: TT_BBox;
|
|
error: TT_Error;
|
|
begin
|
|
|
|
if FOrientation<>0 then
|
|
begin
|
|
error := TT_Get_Glyph_Outline(FGlyphData, outline{%H-});
|
|
if error=TT_Err_Ok then
|
|
error := TT_Get_Outline_BBox(outline, bbox{%H-});
|
|
end else
|
|
begin
|
|
error := TT_Get_Glyph_Metrics(FGlyphData, metrics);
|
|
if error=TT_Err_Ok then
|
|
bbox := metrics.bbox;
|
|
end;
|
|
|
|
if error=TT_Err_Ok then
|
|
with bbox do
|
|
result := rect(IncludeFullGrainMin(xMin+round(x*64),64) div 64,
|
|
IncludeFullGrainMin(-yMax+round(y*64),64) div 64,
|
|
(IncludeFullGrainMax(xMax+round(x*64),64)+1) div 64,
|
|
(IncludeFullGrainMax(-yMin+round(y*64),64)+1) div 64)
|
|
else
|
|
result := TRect.Empty;
|
|
end;
|
|
|
|
constructor TFreeTypeGlyph.Create(AFont: TFreeTypeFont; AIndex: integer);
|
|
begin
|
|
if not AFont.CheckFace or (TT_New_Glyph(AFont.FFace, FGlyphData) <> TT_Err_Ok) then
|
|
raise EFreeType.Create('Cannot create empty glyph');
|
|
FLoaded := AFont.LoadGlyphInto(FGlyphData, AIndex);
|
|
FIndex := AIndex;
|
|
end;
|
|
|
|
constructor TFreeTypeGlyph.create;
|
|
begin
|
|
end;
|
|
|
|
function TFreeTypeGlyph.Clone(AOrientation: Integer): TFreeTypeGlyph;
|
|
begin
|
|
if not FLoaded then
|
|
raise EFreeType.Create('Cannot create a clone of an empty glyph');
|
|
|
|
result := TFreeTypeGlyph.create;
|
|
result.FLoaded := FLoaded;
|
|
result.FIndex := FIndex;
|
|
result.FOrientation := AOrientation;
|
|
|
|
TT_Copy_Glyph(FGlyphData, result.FGlyphData);
|
|
end;
|
|
|
|
function TFreeTypeGlyph.RenderDirectly(x, y: single; Rect: TRect;
|
|
OnRender: TDirectRenderingFunction; quality : TGlyphRenderQuality; ClearType: boolean): boolean;
|
|
begin
|
|
result := RenderDirectly(TTGetDefaultRasterizer, x,y, Rect, OnRender, quality, ClearType);
|
|
end;
|
|
|
|
function TFreeTypeGlyph.RenderDirectly(ARasterizer: TFreeTypeRasterizer; x,
|
|
y: single; Rect: TRect; OnRender: TDirectRenderingFunction;
|
|
quality: TGlyphRenderQuality; ClearType: boolean): boolean;
|
|
var mono: TFreeTypeMonochromeMap;
|
|
tx,xb,yb: integer;
|
|
pdest: pbyte;
|
|
buf: pointer;
|
|
glyphBounds: TRect;
|
|
begin
|
|
if ClearType then
|
|
begin
|
|
Rect.Left *= 3;
|
|
Rect.Right *= 3;
|
|
x *= 3;
|
|
end;
|
|
|
|
glyphBounds := BoundsWithOffset[x,y];
|
|
|
|
if ClearType then
|
|
begin
|
|
InflateRect(glyphBounds,1,0);
|
|
glyphBounds.Left := IncludeFullGrainMin( glyphBounds.Left, 3);
|
|
glyphBounds.Right := IncludeFullGrainMax( glyphBounds.Right-1, 3) + 1;
|
|
end;
|
|
if not IntersectRect(Rect,Rect,glyphBounds) then exit(False);
|
|
|
|
case quality of
|
|
grqMonochrome:
|
|
begin
|
|
tx := rect.right-rect.left;
|
|
mono := TFreeTypeMonochromeMap.Create(ARasterizer,tx,rect.bottom-rect.top);
|
|
result := mono.RenderGlyph(self,x-rect.left,y-rect.top);
|
|
if result then
|
|
begin
|
|
getmem(buf, tx);
|
|
for yb := mono.Height-1 downto 0 do
|
|
begin
|
|
mono.ScanMoveTo(0,yb);
|
|
pdest := pbyte(buf);
|
|
for xb := tx-1 downto 0 do
|
|
begin
|
|
if mono.ScanNextPixel then
|
|
pdest^ := $ff
|
|
else
|
|
pdest^ := 0;
|
|
inc(pdest);
|
|
end;
|
|
OnRender(rect.Left,rect.top+yb,tx,buf);
|
|
end;
|
|
freemem(buf);
|
|
end;
|
|
mono.Free;
|
|
end;
|
|
grqLowQuality:
|
|
begin
|
|
ARasterizer.Set_Raster_Palette(RegularGray5);
|
|
result := TT_Render_Directly_Glyph_Gray(FGlyphData,
|
|
round((x-rect.left)*64), round((rect.bottom-y)*64),
|
|
rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top,
|
|
OnRender, ARasterizer) = TT_Err_Ok;
|
|
end;
|
|
grqHighQuality:
|
|
result := TT_Render_Directly_Glyph_HQ(FGlyphData,
|
|
round((x-rect.left)*64), round((rect.bottom-y)*64),
|
|
rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top,
|
|
OnRender, ARasterizer) = TT_Err_Ok;
|
|
else
|
|
result := false{%H-};
|
|
end;
|
|
end;
|
|
|
|
destructor TFreeTypeGlyph.Destroy;
|
|
begin
|
|
if FreeTypeInitialized then
|
|
TT_Done_Glyph(FGlyphData);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
{ TFreeTypeFont }
|
|
|
|
procedure TFreeTypeFont.LoadFace;
|
|
var errorNum: TT_Error;
|
|
familyItem: TCustomFamilyCollectionItem;
|
|
fontItem: TCustomFontCollectionItem;
|
|
begin
|
|
DiscardFace;
|
|
if FStream <> nil then
|
|
begin
|
|
errorNum := TT_Open_Face(FStream,False,FFace);
|
|
if errorNum <> TT_Err_Ok then
|
|
raise EFreeType.Create('Cannot open font (TT_Error ' + intToStr(errorNum)+') <Stream>');
|
|
end else
|
|
begin
|
|
if Pos(PathDelim, FName) <> 0 then
|
|
begin
|
|
errorNum := TT_Open_Face(FName,FFace);
|
|
if errorNum <> TT_Err_Ok then
|
|
raise EFreeType.Create('Cannot open font (TT_Error ' + intToStr(errorNum)+') "'+FName+'"');
|
|
end else
|
|
begin
|
|
familyItem := Collection.Family[FName];
|
|
if familyItem = nil then
|
|
raise EFreeType.Create('Font family not found ("'+FName+'")');
|
|
fontItem := familyItem.GetFont(FStyleStr);
|
|
if fontItem = nil then
|
|
raise EFreeType.Create('Font style not found ("'+FStyleStr+'")');
|
|
FFace := fontItem.QueryFace(FontCollectionItemDestroyListener(self,@OnDestroyFontItem));
|
|
FFaceItem := fontItem;
|
|
end;
|
|
end;
|
|
|
|
FFaceLoaded:= true;
|
|
UpdateInstance;
|
|
end;
|
|
|
|
procedure TFreeTypeFont.SetName(const AValue: String);
|
|
begin
|
|
DiscardStream;
|
|
if FName=AValue then exit;
|
|
FName := AValue;
|
|
FaceChanged;
|
|
end;
|
|
|
|
{$push}
|
|
{$hints off}
|
|
function TFreeTypeFont.GetDPI: integer;
|
|
var metrics: TT_Instance_Metrics;
|
|
begin
|
|
if not CheckInstance then
|
|
begin
|
|
result := FDPI;
|
|
end
|
|
else
|
|
begin
|
|
if TT_Get_Instance_Metrics(FInstance,metrics) = TT_Err_Ok then
|
|
result := metrics.y_resolution
|
|
else
|
|
result := FDPI;
|
|
end;
|
|
end;
|
|
{$pop}
|
|
|
|
function TFreeTypeFont.GetFamily: string;
|
|
begin
|
|
result := Information[ftiFamily];
|
|
end;
|
|
|
|
function TFreeTypeFont.GetFreeTypeStyles: TFreeTypeStyles;
|
|
var a: array of string;
|
|
i: integer;
|
|
begin
|
|
result := [];
|
|
a := StylesToArray(StyleAsString);
|
|
for i := 0 to high(a) do
|
|
if a[i] = 'Bold' then result += [ftsBold] else
|
|
if (a[i] = 'Italic') or (a[i] = 'Oblique') then result += [ftsItalic];
|
|
end;
|
|
|
|
function TFreeTypeFont.FindGlyphNode(Index: Integer): TAvlTreeNode;
|
|
var DataValue: integer;
|
|
begin
|
|
Result:=FGlyphTable.Root;
|
|
while (Result<>nil) do begin
|
|
DataValue := TFreeTypeGlyph(Result.Data).Index;
|
|
if Index=DataValue then exit;
|
|
if Index<DataValue then begin
|
|
Result:=Result.Left
|
|
end else begin
|
|
Result:=Result.Right
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TFreeTypeFont.GetAscent: single;
|
|
begin
|
|
CheckInstance;
|
|
result := FAscentValue*SizeInPixels;
|
|
end;
|
|
|
|
function TFreeTypeFont.GetCapHeight: single;
|
|
begin
|
|
CheckInstance;
|
|
result := FCapHeight*SizeInPixels;
|
|
end;
|
|
|
|
function TFreeTypeFont.GetClearType: boolean;
|
|
begin
|
|
Result:= FClearType;
|
|
end;
|
|
|
|
function TFreeTypeFont.GetCharIndex(AUnicodeChar: integer): integer;
|
|
begin
|
|
if not CheckInstance then
|
|
begin
|
|
result := 0;
|
|
exit;
|
|
end;
|
|
if FCharmapOk then
|
|
begin
|
|
if FCharmapSymbol then
|
|
result := TT_Char_Index(FCharMap, AUnicodeChar or $F000)
|
|
else
|
|
result := TT_Char_Index(FCharMap, AUnicodeChar);
|
|
end
|
|
else
|
|
result := 0;
|
|
end;
|
|
|
|
function TFreeTypeFont.GetDescent: single;
|
|
begin
|
|
CheckInstance;
|
|
result := FDescentValue*SizeInPixels;
|
|
end;
|
|
|
|
function TFreeTypeFont.GetGlyph(Index: integer): TFreeTypeGlyph;
|
|
var
|
|
node: TAvlTreeNode;
|
|
lGlyph: TFreeTypeGlyph;
|
|
begin
|
|
if not CheckInstance then
|
|
begin
|
|
result := nil;
|
|
exit;
|
|
end;
|
|
node := FindGlyphNode(Index);
|
|
if node = nil then
|
|
begin
|
|
lGlyph := TFreeTypeGlyph.Create(self, Index);;
|
|
FGlyphTable.Add(lGlyph);
|
|
end else
|
|
lGlyph := TFreeTypeGlyph(node.Data);
|
|
result := lGlyph;
|
|
end;
|
|
|
|
{$push}
|
|
{$hints off}
|
|
function TFreeTypeFont.GetGlyphCount: integer;
|
|
var prop : TT_Face_Properties;
|
|
begin
|
|
if not CheckFace then
|
|
result := 0
|
|
else
|
|
begin
|
|
if TT_Get_Face_Properties(FFace, prop) <> TT_Err_Ok then
|
|
result := 0
|
|
else
|
|
result := prop.num_glyphs;
|
|
end;
|
|
end;
|
|
|
|
function TFreeTypeFont.GetInformation(AIndex: TFreeTypeInformation): string;
|
|
begin
|
|
if FNamesArray = nil then FetchNames;
|
|
if (ord(AIndex) < 0) or (ord(AIndex) > high(FNamesArray)) then
|
|
result := ''
|
|
else
|
|
result := FNamesArray[ord(AIndex)];
|
|
end;
|
|
{$pop}
|
|
|
|
function TFreeTypeFont.GetGlyphKerning(AGlyphLeft, AGlyphRight: integer): TFreeTypeKerning;
|
|
var
|
|
kerningInfo: TT_KerningInfo;
|
|
factor: single;
|
|
begin
|
|
kerningInfo := TT_Get_KerningInfo(FFace, AGlyphLeft, AGlyphRight);
|
|
factor := SizeInPixels/FUnitsPerEM;
|
|
result.Kerning.x := kerningInfo.kerning_x*factor;
|
|
result.Kerning.y := kerningInfo.kerning_y*factor;
|
|
result.Minimum.x := kerningInfo.minimum_x*factor;
|
|
result.Minimum.y := kerningInfo.minimum_y*factor;
|
|
result.Found := kerningInfo.found;
|
|
end;
|
|
|
|
function TFreeTypeFont.GetLineFullHeight: single;
|
|
begin
|
|
CheckInstance;
|
|
result := (FAscentValue + FDescentValue)*SizeInPixels + GetLineSpacing;
|
|
end;
|
|
|
|
function TFreeTypeFont.GetLineSpacing: single;
|
|
begin
|
|
CheckInstance;
|
|
if not SmallLinePadding then
|
|
result := FLargeLineGapValue*SizeInPixels
|
|
else
|
|
result := FLineGapValue*SizeInPixels;
|
|
end;
|
|
|
|
procedure TFreeTypeFont.OnDestroyFontItem;
|
|
begin
|
|
DiscardFace;
|
|
FaceChanged;
|
|
end;
|
|
|
|
function TFreeTypeFont.GetPixelSize: single;
|
|
begin
|
|
result := SizeInPoints * DPI / 72;
|
|
end;
|
|
|
|
function TFreeTypeFont.GetVersionNumber: string;
|
|
var VersionStr: string;
|
|
idxStart,idxEnd: integer;
|
|
begin
|
|
VersionStr := Information[ftiVersionString];
|
|
idxStart := 1;
|
|
while (idxStart < length(VersionStr)) and not (VersionStr[idxStart] in['0'..'9']) do
|
|
inc(idxStart);
|
|
idxEnd := idxStart;
|
|
while (idxEnd < length(VersionStr)) and (VersionStr[idxEnd+1] in['0'..'9']) do inc(idxEnd);
|
|
if (idxEnd < length(VersionStr)) and (VersionStr[idxEnd+1] = '.') then inc(idxEnd);
|
|
while (idxEnd < length(VersionStr)) and (VersionStr[idxEnd+1] in['0'..'9']) do inc(idxEnd);
|
|
result := copy(VersionStr,idxStart,idxEnd-idxStart+1);
|
|
end;
|
|
|
|
procedure TFreeTypeFont.SetClearType(const AValue: boolean);
|
|
begin
|
|
if FClearType=AValue then exit;
|
|
FClearType:=AValue;
|
|
UpdateSizeInPoints;
|
|
end;
|
|
|
|
procedure TFreeTypeFont.SetDPI(const AValue: integer);
|
|
begin
|
|
if FDPI = AValue then exit;
|
|
FDPI := AValue;
|
|
if FInstanceCreated then
|
|
begin
|
|
TT_Set_Instance_Resolutions(FInstance, AValue,AValue);
|
|
UpdateSizeInPoints;
|
|
end;
|
|
end;
|
|
|
|
procedure TFreeTypeFont.SetFreeTypeStyles(AValue: TFreeTypeStyles);
|
|
var str: string;
|
|
begin
|
|
str := '';
|
|
if ftsBold in AValue then str += 'Bold ';
|
|
if ftsItalic in AValue then str += 'Italic ';
|
|
StyleAsString := trim(str);
|
|
end;
|
|
|
|
procedure TFreeTypeFont.SetHinted(const AValue: boolean);
|
|
begin
|
|
if FHinted=AValue then exit;
|
|
FHinted:=AValue;
|
|
FGlyphTable.FreeAndClear;
|
|
end;
|
|
|
|
function TFreeTypeFont.GetHinted: boolean;
|
|
begin
|
|
result := FHinted;
|
|
end;
|
|
|
|
procedure TFreeTypeFont.SetLineFullHeight(AValue: single);
|
|
var Ratio: single;
|
|
begin
|
|
CheckInstance;
|
|
Ratio := FAscentValue + FDescentValue;
|
|
if not SmallLinePadding then
|
|
Ratio += FLargeLineGapValue
|
|
else
|
|
Ratio += FLineGapValue;
|
|
if Ratio <> 0 then
|
|
SizeInPixels := AValue / Ratio
|
|
else
|
|
SizeInPixels := AValue;
|
|
end;
|
|
|
|
procedure TFreeTypeFont.SetStyleAsString(AValue: string);
|
|
begin
|
|
AValue := Trim(AValue);
|
|
if FStyleStr=AValue then Exit;
|
|
FStyleStr:=AValue;
|
|
FaceChanged;
|
|
end;
|
|
|
|
procedure TFreeTypeFont.DiscardFace;
|
|
begin
|
|
if FFaceLoaded then
|
|
begin
|
|
DiscardInstance;
|
|
if FFaceItem <> nil then
|
|
begin
|
|
FFaceItem.ReleaseFace(FontCollectionItemDestroyListener(self,@OnDestroyFontItem));
|
|
FFaceItem := nil;
|
|
end
|
|
else
|
|
TT_Close_Face(FFace);
|
|
FFaceLoaded := false;
|
|
FNamesArray := nil;
|
|
end;
|
|
FCharmapOk := false;
|
|
end;
|
|
|
|
procedure TFreeTypeFont.DiscardInstance;
|
|
begin
|
|
if FInstanceCreated then
|
|
begin
|
|
if FreeTypeInitialized then
|
|
TT_Done_Instance(FInstance);
|
|
FInstanceCreated := false;
|
|
FGlyphTable.FreeAndClear;
|
|
end;
|
|
end;
|
|
|
|
procedure TFreeTypeFont.DiscardStream;
|
|
begin
|
|
if FStream <> nil then
|
|
begin
|
|
DiscardFace;
|
|
if FOwnedStream then FStream.Free;
|
|
FStream := nil;
|
|
FOwnedStream:= false;
|
|
end;
|
|
end;
|
|
|
|
procedure TFreeTypeFont.SetPixelSize(const AValue: single);
|
|
begin
|
|
SizeInPoints := AValue*72/DPI;
|
|
end;
|
|
|
|
procedure TFreeTypeFont.SetPointSize(AValue: single);
|
|
begin
|
|
if AValue < FreeTypeMinPointSize then AValue := FreeTypeMinPointSize;
|
|
if FPointSize=AValue then exit;
|
|
FPointSize:=AValue;
|
|
UpdateSizeInPoints;
|
|
end;
|
|
|
|
function TFreeTypeFont.LoadGlyphInto(_glyph: TT_Glyph; glyph_index: Word): boolean;
|
|
var flags: integer;
|
|
begin
|
|
if not CheckInstance then
|
|
raise EFreeType.Create('No font instance');
|
|
flags := TT_Load_Scale_Glyph;
|
|
if FHinted then flags := flags or TT_Load_Hint_Glyph;
|
|
result := (TT_Load_Glyph(FInstance, _glyph, glyph_index, flags) = TT_Err_Ok);
|
|
end;
|
|
|
|
procedure TFreeTypeFont.SetWidthFactor(const AValue: single);
|
|
begin
|
|
if FWidthFactor=AValue then exit;
|
|
FWidthFactor:=AValue;
|
|
FGlyphTable.FreeAndClear;
|
|
UpdateSizeInPoints;
|
|
end;
|
|
|
|
procedure TFreeTypeFont.UpdateInstance;
|
|
var
|
|
errorNum: TT_Error;
|
|
begin
|
|
DiscardInstance;
|
|
|
|
errorNum := TT_New_Instance(FFace, FInstance);
|
|
if errorNum = TT_Err_Ok then
|
|
begin
|
|
FInstanceCreated := true;
|
|
TT_Set_Instance_Resolutions(FInstance, FDPI,FDPI);
|
|
UpdateSizeInPoints;
|
|
UpdateMetrics;
|
|
UpdateCharmap;
|
|
end else
|
|
raise EFreeType.Create('Cannot create font instance (TT_Error ' + intToStr(errorNum)+')');
|
|
end;
|
|
|
|
procedure TFreeTypeFont.UpdateSizeInPoints;
|
|
var charsizex: integer;
|
|
begin
|
|
if FInstanceCreated then
|
|
begin
|
|
if not FClearType then
|
|
charsizex := round(FPointSize*64*FWidthFactor)
|
|
else
|
|
charsizex := round(FPointSize*64*FWidthFactor*3);
|
|
|
|
if TT_Set_Instance_CharSizes(FInstance,charsizex,round(FPointSize*64)) <> TT_Err_Ok then
|
|
raise EFreeType.Create('Unable to set point size');
|
|
FGlyphTable.FreeAndClear;
|
|
end;
|
|
end;
|
|
|
|
procedure TFreeTypeFont.UpdateMetrics;
|
|
var prop: TT_Face_Properties;
|
|
begin
|
|
if CheckFace then
|
|
begin
|
|
TT_Get_Face_Properties(FFace,prop);
|
|
FAscentValue := prop.horizontal^.ascender;
|
|
FDescentValue := prop.horizontal^.descender;
|
|
FLineGapValue:= prop.horizontal^.line_gap;
|
|
FLargeLineGapValue:= FLineGapValue;
|
|
FUnitsPerEM := prop.header^.units_per_EM;
|
|
|
|
if (FAscentValue = 0) and (FDescentValue = 0) then
|
|
begin
|
|
if prop.os2^.version <> $ffff then
|
|
begin
|
|
if (prop.os2^.usWinAscent <> 0) or (prop.os2^.usWinDescent <> 0) then
|
|
begin
|
|
FAscentValue := prop.os2^.usWinAscent;
|
|
FDescentValue := -prop.os2^.usWinDescent;
|
|
end else
|
|
begin
|
|
FAscentValue := prop.os2^.sTypoAscender;
|
|
FDescentValue := prop.os2^.sTypoDescender;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
if prop.os2^.version <> $ffff then
|
|
if prop.os2^.sTypoLineGap > FLargeLineGapValue then
|
|
FLargeLineGapValue := prop.os2^.sTypoLineGap;
|
|
|
|
if prop.os2^.version >= 2 then
|
|
FCapHeight:=prop.os2^.sCapHeight
|
|
else
|
|
FCapHeight:=0;
|
|
|
|
FAscentValue /= FUnitsPerEM;
|
|
FDescentValue /= -FUnitsPerEM;
|
|
FLineGapValue /= FUnitsPerEM;
|
|
FLargeLineGapValue /= FUnitsPerEM;
|
|
FCapHeight /= FUnitsPerEM;
|
|
|
|
if FLargeLineGapValue = 0 then
|
|
FLargeLineGapValue := (FAscentValue+FDescentValue)*0.1;
|
|
|
|
end else
|
|
begin
|
|
FAscentValue := -0.5;
|
|
FDescentValue := 0.5;
|
|
FLineGapValue := 0;
|
|
FLargeLineGapValue:= 0;
|
|
FUnitsPerEM := 1;
|
|
end;
|
|
end;
|
|
|
|
procedure TFreeTypeFont.UpdateCharmap;
|
|
var i,n: integer;
|
|
lPlatform,encoding: integer;
|
|
begin
|
|
if FCharmapOk then exit;
|
|
if not FFaceLoaded then
|
|
begin
|
|
FCharmapOk := false;
|
|
exit;
|
|
end;
|
|
|
|
n := TT_Get_CharMap_Count(FFace);
|
|
lPlatform := -1;
|
|
encoding := 0;
|
|
FCharmapSymbol := false;
|
|
|
|
//MS Unicode
|
|
for i := 0 to n-1 do
|
|
begin
|
|
if TT_Get_CharMap_ID(FFace, i, lPlatform, encoding) = TT_Err_Ok then
|
|
begin
|
|
if (lPlatform = TT_PLATFORM_MICROSOFT) and (encoding = 1) then
|
|
begin
|
|
if TT_Get_CharMap(FFace, i, FCharMap) = TT_Err_Ok then
|
|
begin
|
|
FCharmapOk := true;
|
|
exit;
|
|
end
|
|
end else
|
|
if (lPlatform = TT_PLATFORM_MICROSOFT) and (encoding = 0) then
|
|
begin
|
|
if TT_Get_CharMap(FFace, i, FCharMap) = TT_Err_Ok then
|
|
begin
|
|
FCharmapOk := true;
|
|
FCharmapSymbol:= true;
|
|
exit;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
//Apple Unicode
|
|
for i := 0 to n-1 do
|
|
begin
|
|
if TT_Get_CharMap_ID(FFace, i, lPlatform, encoding) = TT_Err_Ok then
|
|
begin
|
|
if (lPlatform = TT_PLATFORM_APPLE_UNICODE) then
|
|
if TT_Get_CharMap(FFace, i, FCharMap) = TT_Err_Ok then
|
|
begin
|
|
FCharmapOk := true;
|
|
exit;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
//ISO Unicode
|
|
for i := 0 to n-1 do
|
|
begin
|
|
if TT_Get_CharMap_ID(FFace, i, lPlatform, encoding) = TT_Err_Ok then
|
|
begin
|
|
if (lPlatform = TT_PLATFORM_ISO) and (encoding = 1) then
|
|
if TT_Get_CharMap(FFace, i, FCharMap) = TT_Err_Ok then
|
|
begin
|
|
FCharmapOk := true;
|
|
exit;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
FCharmapOk := false;
|
|
end;
|
|
|
|
procedure TFreeTypeFont.RenderTextDecoration(AText: string; x, y: single;
|
|
ARect: TRect; OnRender: TDirectRenderingFunction);
|
|
procedure HorizLine(AYCoeff, AHeightCoeff: single);
|
|
var
|
|
ly, height: single;
|
|
clippedRect,unclippedRect: TRect;
|
|
begin
|
|
ly := y + self.Ascent * AYCoeff;
|
|
height := Max(self.Ascent * AHeightCoeff, 1);
|
|
unclippedRect := Types.Rect(round(x),round(ly),
|
|
round(x+self.TextWidth(AText)),round(ly+height));
|
|
clippedRect := rect(0,0,0,0);
|
|
if IntersectRect(clippedRect,unclippedRect,ARect) then
|
|
FillRect(clippedRect,OnRender);
|
|
end;
|
|
begin
|
|
if UnderlineDecoration then
|
|
HorizLine(+1.5*0.08, 0.08);
|
|
if StrikeoutDecoration then
|
|
HorizLine(-0.3, 0.06);
|
|
end;
|
|
|
|
procedure TFreeTypeFont.FillRect(ARect: TRect; OnRender: TDirectRenderingFunction);
|
|
var
|
|
yb,temp,tx: integer;
|
|
data: pbyte;
|
|
begin
|
|
if ARect.Top > ARect.Bottom then
|
|
begin
|
|
temp := ARect.Top;
|
|
ARect.Top := ARect.Bottom;
|
|
ARect.Bottom := temp;
|
|
end;
|
|
if ARect.Left > ARect.Right then
|
|
begin
|
|
temp := ARect.Left;
|
|
ARect.Left := ARect.Right;
|
|
ARect.Right:= temp;
|
|
end;
|
|
if ClearType then
|
|
begin
|
|
ARect.Left *= 3;
|
|
ARect.Right *= 3;
|
|
end;
|
|
tx := ARect.Right-ARect.Left;
|
|
if tx > 0 then
|
|
begin
|
|
getmem(data,tx);
|
|
try
|
|
fillchar(data^, tx, 255);
|
|
for yb := ARect.Top to ARect.Bottom-1 do
|
|
OnRender(ARect.Left,yb,tx,data);
|
|
finally
|
|
freemem(data);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TFreeTypeFont.FaceChanged;
|
|
begin
|
|
if not FFaceChanged then
|
|
begin
|
|
FFaceChanged := true;
|
|
DiscardFace;
|
|
end;
|
|
end;
|
|
|
|
constructor TFreeTypeFont.Create;
|
|
begin
|
|
EnsureFreeTypeInitialized;
|
|
FFaceLoaded := false;
|
|
FFaceItem := nil;
|
|
FInstanceCreated := false;
|
|
FCharmapOk := false;
|
|
FPointSize := 10;
|
|
FDPI := 96;
|
|
FGlyphTable := TAvlTree.Create;
|
|
FGlyphTable.OnCompare := @GlyphTableOnCompare;
|
|
FHinted := true;
|
|
FKerningEnabled:= true;
|
|
FKerningFallbackEnabled:= true;
|
|
FWidthFactor := 1;
|
|
FClearType := false;
|
|
FStyleStr:= 'Regular';
|
|
SmallLinePadding:= true;
|
|
Quality := grqHighQuality;
|
|
FFaceChanged := true;
|
|
end;
|
|
|
|
destructor TFreeTypeFont.Destroy;
|
|
begin
|
|
DiscardInstance;
|
|
DiscardFace;
|
|
DiscardStream;
|
|
FGlyphTable.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TFreeTypeFont.AccessFromStream(AStream: TStream; AStreamOwner: boolean);
|
|
begin
|
|
DiscardStream;
|
|
FStream := AStream;
|
|
FOwnedStream:= AStreamOwner;
|
|
FaceChanged;
|
|
end;
|
|
|
|
procedure TFreeTypeFont.RenderText(AText: string; x, y: single; ARect: TRect;
|
|
OnRender: TDirectRenderingFunction);
|
|
var
|
|
pstr: pchar;
|
|
left,charcode,charlen: integer;
|
|
idx: integer;
|
|
g: TFreeTypeGlyph;
|
|
prevCharcode, glyphIndex: integer;
|
|
txmatrix: TT_Matrix;
|
|
angle, sin_angle, cos_angle: single;
|
|
outline: ^TT_Outline;
|
|
vector: TT_Vector;
|
|
corrX, corrY: single;
|
|
begin
|
|
if not CheckInstance then exit;
|
|
if AText = '' then exit;
|
|
idx := pos(LineEnding,AText);
|
|
|
|
if Orientation<>0 then
|
|
begin
|
|
angle := Orientation * PI / 1800;
|
|
SinCos(angle, sin_angle, cos_angle);
|
|
txmatrix.xx := Round( cos_angle * $10000 );
|
|
txmatrix.xy := -Round( sin_angle * $10000 );
|
|
txmatrix.yx := Round( sin_angle * $10000 );
|
|
txmatrix.yy := Round( cos_angle * $10000 );
|
|
end;
|
|
|
|
while idx <> 0 do
|
|
begin
|
|
RenderText(copy(AText,1,idx-1),x,y,ARect,OnRender);
|
|
delete(AText,1,idx+length(LineEnding)-1);
|
|
if Orientation<>0 then
|
|
begin
|
|
vector.x := 0;
|
|
vector.y := -round(LineFullHeight * 64);
|
|
TT_Transform_Vector(vector.x, vector.y, txmatrix);
|
|
x += vector.x / 64;
|
|
y -= vector.y / 64;
|
|
end else
|
|
y += LineFullHeight;
|
|
idx := pos(LineEnding,AText);
|
|
end;
|
|
If Assigned(FOnRenderText) then
|
|
FOnRenderText(AText,x,y);
|
|
|
|
// TODO: Rotation at arbitraty angles requires antialiased drawing
|
|
RenderTextDecoration(AText,x,y,ARect,OnRender);
|
|
pstr := @AText[1];
|
|
left := length(AText);
|
|
prevCharcode := -1;
|
|
while left > 0 do
|
|
begin
|
|
charcode := UTF8CodepointToUnicode(pstr, charlen);
|
|
dec(left,charlen);
|
|
glyphIndex := CharIndex[charcode];
|
|
g := Glyph[glyphIndex];
|
|
if Orientation<>0 then
|
|
g := g.Clone(Orientation);
|
|
|
|
if g <> nil then
|
|
with g do
|
|
begin
|
|
corrX := Advance;
|
|
|
|
if KerningEnabled and (prevCharcode <> -1) then
|
|
corrX += round(GetCharKerning(prevCharcode, charcode).Kerning.x);
|
|
|
|
vector.x := round(corrX * 64);
|
|
vector.y := 0;
|
|
|
|
if Orientation<>0 then begin
|
|
outLine := @PGlyph(Data.z)^.outline;
|
|
TT_Transform_Outline(outline^, txmatrix);
|
|
TT_Transform_Vector(vector.x, vector.y, txmatrix);
|
|
end;
|
|
|
|
corrX := vector.x / 64;
|
|
corry := vector.y / 64;
|
|
|
|
if Hinted then
|
|
RenderDirectly(x,round(y),ARect,OnRender,quality,FClearType)
|
|
else
|
|
RenderDirectly(x,y,ARect,OnRender,quality,FClearType);
|
|
|
|
if FClearType then
|
|
x += corrX/3
|
|
else
|
|
x += corrX;
|
|
|
|
y -= corrY;
|
|
|
|
prevCharcode := charcode;
|
|
if Orientation<>0 then
|
|
g.Free;
|
|
end;
|
|
inc(pstr,charlen);
|
|
end;
|
|
end;
|
|
|
|
procedure TFreeTypeFont.RenderGlyph(AGlyph: Integer; x, y: single;
|
|
ARect: TRect; OnRender: TDirectRenderingFunction);
|
|
var
|
|
g: TFreeTypeGlyph;
|
|
begin
|
|
if not CheckInstance then exit;
|
|
g := Glyph[AGlyph];
|
|
if g <> nil then
|
|
with g do
|
|
begin
|
|
if Hinted then
|
|
RenderDirectly(x,round(y),ARect,OnRender,quality,FClearType)
|
|
else
|
|
RenderDirectly(x,y,ARect,OnRender,quality,FClearType);
|
|
end;
|
|
end;
|
|
|
|
procedure TFreeTypeFont.SetNameAndStyle(AName: string; AStyle: string);
|
|
begin
|
|
AStyle := Trim(AStyle);
|
|
if (AName = FName) and (AStyle = FStyleStr) then exit;
|
|
FName := AName;
|
|
FStyleStr := AStyle;
|
|
FaceChanged;
|
|
end;
|
|
|
|
procedure TFreeTypeFont.SetNameAndStyle(AName: string; AStyle: TFreeTypeStyles);
|
|
var styleStr: string;
|
|
begin
|
|
styleStr := '';
|
|
if ftsBold in AStyle then styleStr += 'Bold ';
|
|
if ftsItalic in AStyle then styleStr += 'Italic ';
|
|
SetNameAndStyle(AName, Trim(styleStr));
|
|
end;
|
|
|
|
function TFreeTypeFont.TextWidth(AText: string): single;
|
|
var
|
|
pstr: pchar;
|
|
left,charcode,charlen: integer;
|
|
maxWidth,w: single;
|
|
idx: integer;
|
|
g: TFreeTypeGlyph;
|
|
prevCharcode, glyphIndex: integer;
|
|
begin
|
|
result := 0;
|
|
if not CheckInstance then exit;
|
|
if AText = '' then exit;
|
|
|
|
maxWidth := 0;
|
|
idx := pos(LineEnding,AText);
|
|
while idx <> 0 do
|
|
begin
|
|
w := TextWidth(copy(AText,1,idx-1));
|
|
if w > maxWidth then maxWidth:= w;
|
|
delete(AText,1,idx+length(LineEnding)-1);
|
|
idx := pos(LineEnding,AText);
|
|
end;
|
|
if AText = '' then
|
|
begin
|
|
result := maxWidth;
|
|
exit;
|
|
end;
|
|
|
|
pstr := @AText[1];
|
|
left := length(AText);
|
|
prevCharcode := -1;
|
|
while left > 0 do
|
|
begin
|
|
charcode := UTF8CodepointToUnicode(pstr, charlen);
|
|
inc(pstr,charlen);
|
|
dec(left,charlen);
|
|
glyphIndex := CharIndex[charcode];
|
|
g := Glyph[glyphIndex];
|
|
if g <> nil then
|
|
with g do
|
|
begin
|
|
if KerningEnabled and (prevCharcode <> -1) then
|
|
result += GetCharKerning(prevCharcode, charcode).Kerning.x;
|
|
if FClearType then
|
|
result += Advance/3
|
|
else
|
|
result += Advance;
|
|
prevCharcode := charcode;
|
|
end;
|
|
end;
|
|
if maxWidth > result then
|
|
result := maxWidth;
|
|
end;
|
|
|
|
function TFreeTypeFont.TextHeight(AText: string): single;
|
|
var idx: integer;
|
|
nb: integer;
|
|
begin
|
|
if AText= '' then result := 0
|
|
else
|
|
begin
|
|
result := LineFullHeight;
|
|
nb := 1;
|
|
idx := pos(LineEnding,AText);
|
|
while idx <> 0 do
|
|
begin
|
|
nb += 1;
|
|
delete(AText,1,idx+length(LineEnding)-1);
|
|
idx := pos(LineEnding,AText);
|
|
end;
|
|
result *= nb;
|
|
end;
|
|
end;
|
|
|
|
function TFreeTypeFont.CharWidthFromUnicode(AUnicodeChar: integer): single;
|
|
var g: TFreeTypeGlyph;
|
|
begin
|
|
g := Glyph[CharIndex[AUnicodeChar]];
|
|
if g = nil then result := 0
|
|
else
|
|
result := g.Advance;
|
|
if FClearType then result /= 3;
|
|
end;
|
|
|
|
function TFreeTypeFont.CharWidthFromGlyph(AGlyph: integer): single;
|
|
var g: TFreeTypeGlyph;
|
|
begin
|
|
g := Glyph[AGlyph];
|
|
if g = nil then result := 0
|
|
else
|
|
result := g.Advance;
|
|
if FClearType then result /= 3;
|
|
end;
|
|
|
|
function TFreeTypeFont.CharsWidth(AText: string): ArrayOfSingle;
|
|
var
|
|
pstr: pchar;
|
|
left,charcode,charlen: integer;
|
|
resultIndex: integer;
|
|
w: single;
|
|
prevCharcode,glyphIndex: integer;
|
|
g: TFreeTypeGlyph;
|
|
begin
|
|
if AText = '' then
|
|
begin
|
|
setlength(result{%H-}, 0);
|
|
exit;
|
|
end;
|
|
pstr := @AText[1];
|
|
left := length(AText);
|
|
setlength(result, UTF8Length(AText));
|
|
resultIndex := 0;
|
|
prevCharcode := -1;
|
|
while left > 0 do
|
|
begin
|
|
charcode := UTF8CodepointToUnicode(pstr, charlen);
|
|
inc(pstr,charlen);
|
|
dec(left,charlen);
|
|
|
|
glyphIndex := CharIndex[charcode];
|
|
g := Glyph[glyphIndex];
|
|
if g <> nil then
|
|
with g do
|
|
begin
|
|
if FClearType then
|
|
w := Advance/3
|
|
else
|
|
w := Advance;
|
|
if KerningEnabled and (prevCharcode <> -1) and (resultIndex > 0) then
|
|
result[resultIndex-1] += GetCharKerning(prevCharcode, charcode).Kerning.x;
|
|
prevCharcode := charcode;
|
|
end else
|
|
w := 0;
|
|
|
|
if resultIndex >= length(result) then
|
|
setlength(result, resultIndex+1);
|
|
result[resultIndex] := w;
|
|
inc(resultIndex);
|
|
end;
|
|
setlength(result, resultIndex);
|
|
end;
|
|
|
|
function TFreeTypeFont.CharsPosition(AText: string): ArrayOfCharPosition;
|
|
begin
|
|
result := CharsPosition(AText, []);
|
|
end;
|
|
|
|
function TFreeTypeFont.CharsPosition(AText: string; AAlign: TFreeTypeAlignments): ArrayOfCharPosition;
|
|
var
|
|
resultIndex,resultLineStart: integer;
|
|
curX: single;
|
|
|
|
procedure ApplyHorizAlign;
|
|
var delta: single;
|
|
i: integer;
|
|
begin
|
|
if ftaLeft in AAlign then exit;
|
|
if ftaCenter in AAlign then
|
|
delta := -curX/2
|
|
else if ftaRight in AAlign then
|
|
delta := -curX
|
|
else
|
|
exit;
|
|
|
|
for i := resultLineStart to resultIndex-1 do
|
|
result[i].x += delta;
|
|
end;
|
|
|
|
var
|
|
pstr: pchar;
|
|
left,charcode,charlen: integer;
|
|
i : integer;
|
|
w,h,y,yTopRel,yBottomRel: single;
|
|
Found: boolean;
|
|
StrLineEnding: string; // a string version of LineEnding, don't remove or else wont compile in UNIXes
|
|
g: TFreeTypeGlyph;
|
|
prevCharcode, glyphIndex: integer;
|
|
begin
|
|
result := nil;
|
|
if not CheckInstance then exit;
|
|
if AText = '' then exit;
|
|
StrLineEnding := LineEnding;
|
|
pstr := @AText[1];
|
|
left := length(AText);
|
|
setlength(result, UTF8Length(AText)+1);
|
|
resultIndex := 0;
|
|
resultLineStart := 0;
|
|
if ftaLeft in AAlign then AAlign -= [ftaLeft, ftaCenter, ftaRight];
|
|
if ftaBaseline in AAlign then AAlign -= [ftaTop, ftaBaseline, ftaBottom, ftaVerticalCenter];
|
|
curX := 0;
|
|
y := 0;
|
|
if ftaTop in AAlign then
|
|
begin
|
|
y += Ascent;
|
|
AAlign -= [ftaTop, ftaBottom, ftaVerticalCenter];
|
|
end;
|
|
yTopRel := -Ascent;
|
|
yBottomRel := Descent;
|
|
h := LineFullHeight;
|
|
prevCharcode := -1;
|
|
while left > 0 do
|
|
begin
|
|
if (left > length(StrLineEnding)) and (pstr^ = StrLineEnding[1]) then
|
|
begin
|
|
Found := true;
|
|
for i := 2 to length(StrLineEnding) do
|
|
if (pstr+(i-1))^ <> StrLineEnding[i] then
|
|
begin
|
|
Found := false;
|
|
break;
|
|
end;
|
|
if Found then
|
|
begin
|
|
for i := 1 to length(StrLineEnding) do
|
|
begin
|
|
with result[resultIndex] do
|
|
begin
|
|
x := curX;
|
|
width := 0;
|
|
yTop := y+yTopRel;
|
|
yBase := y;
|
|
yBottom := y+yBottomRel;
|
|
end;
|
|
inc(resultIndex);
|
|
inc(pstr);
|
|
dec(left);
|
|
end;
|
|
ApplyHorizAlign;
|
|
y += h;
|
|
curX := 0;
|
|
resultLineStart := resultIndex;
|
|
prevCharcode := -1;
|
|
if left <= 0 then break;
|
|
end;
|
|
end;
|
|
charcode := UTF8CodepointToUnicode(pstr, charlen);
|
|
inc(pstr,charlen);
|
|
dec(left,charlen);
|
|
glyphIndex := CharIndex[charcode];
|
|
g := Glyph[glyphIndex];
|
|
if g <> nil then
|
|
with g do
|
|
begin
|
|
if FClearType then
|
|
w := Advance/3
|
|
else
|
|
w := Advance;
|
|
if KerningEnabled and (prevCharcode <> -1) then
|
|
curX += GetCharKerning(prevCharcode, charcode).Kerning.x;
|
|
prevCharcode := charcode
|
|
end else
|
|
w := 0;
|
|
if resultIndex >= length(result) then
|
|
setlength(result, resultIndex+1);
|
|
with result[resultIndex] do
|
|
begin
|
|
x := curX;
|
|
width := w;
|
|
yTop := y+yTopRel;
|
|
yBase := y;
|
|
yBottom := y+yBottomRel;
|
|
end;
|
|
inc(resultIndex);
|
|
curX += w;
|
|
end;
|
|
if resultIndex >= length(result) then
|
|
setlength(result, resultIndex+1);
|
|
with result[resultIndex] do
|
|
begin
|
|
x := curX;
|
|
width := 0;
|
|
yTop := y+yTopRel;
|
|
yBase := y;
|
|
yBottom := y+yBottomRel;
|
|
end;
|
|
inc(resultIndex);
|
|
setlength(result, resultIndex);
|
|
ApplyHorizAlign;
|
|
|
|
if ftaBottom in AAlign then
|
|
begin
|
|
y += LineFullHeight-Ascent;
|
|
for i := 0 to high(result) do
|
|
with result[i] do
|
|
begin
|
|
yTop -= y;
|
|
yBase -= y;
|
|
yBottom -= y;
|
|
end;
|
|
end else
|
|
if ftaVerticalCenter in AAlign then
|
|
begin
|
|
y += LineFullHeight/2-Ascent;
|
|
for i := 0 to high(result) do
|
|
with result[i] do
|
|
begin
|
|
yTop -= y;
|
|
yBase -= y;
|
|
yBottom -= y;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TFreeTypeFont.FetchNames;
|
|
const
|
|
maxNameIndex = 22;
|
|
var i,j: integer;
|
|
nrPlatformID,nrEncodingID,nrLanguageID,nrNameID,len: integer;
|
|
value,value2: string;
|
|
|
|
begin
|
|
// setlength(FNamesArray, maxNameIndex+1);
|
|
// wp: Move this into the "if" to avoid ignoring font files after reading defective one.
|
|
if CheckFace then
|
|
begin
|
|
setlength(FNamesArray, maxNameIndex+1);
|
|
for i := 0 to TT_Get_Name_Count(FFace)-1 do
|
|
begin
|
|
if TT_Get_Name_ID(FFace, i, nrPlatformID, nrEncodingID,
|
|
nrLanguageID, nrNameID) <> TT_Err_Ok then continue;
|
|
|
|
if (nrNameID < 0) or (nrNameID > maxNameIndex) then continue;
|
|
|
|
{ check for Microsoft, Unicode, English }
|
|
if ((nrPlatformID=TT_PLATFORM_MICROSOFT) and (nrEncodingID in[0,1]) and
|
|
((nrLanguageID=$0409) or (nrLanguageID=$0809) or
|
|
(nrLanguageID=$0c09) or (nrLanguageID=$1009) or
|
|
(nrLanguageID=$1409) or (nrLanguageID=$1809))) or
|
|
{ or for Unicode, English }
|
|
((nrPlatformID=TT_PLATFORM_APPLE_UNICODE) and
|
|
(nrLanguageID=0)) then
|
|
begin
|
|
value := TT_Get_Name_String(FFace, i);
|
|
if value <> '' then
|
|
begin
|
|
for j := 1 to length(value) div 2 do
|
|
pword(@value[j*2-1])^ := BEtoN(pword(@value[j*2-1])^);
|
|
setlength(value2{%H-}, 3*(length(value) div 2) + 1); //maximum is 3-byte chars and NULL char at the end
|
|
len := system.UnicodeToUtf8(@value2[1],length(value2),PUnicodeChar( @value[1] ),length(value) div 2);
|
|
if len > 0 then
|
|
begin
|
|
setlength(value2, len-1 );
|
|
value := value2;
|
|
end;
|
|
end;
|
|
FNamesArray[nrNameID] := value;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TFreeTypeFont.GetCollection: TCustomFreeTypeFontCollection;
|
|
begin
|
|
if FCollection = nil then
|
|
result := FontCollection
|
|
else
|
|
result := FCollection;
|
|
end;
|
|
|
|
function TFreeTypeFont.CheckFace: boolean;
|
|
begin
|
|
if FFaceChanged then
|
|
begin
|
|
FFaceChanged:= false;
|
|
LoadFace;
|
|
end;
|
|
result := FFaceLoaded;
|
|
end;
|
|
|
|
function TFreeTypeFont.GetCharKerning(AUnicodeCharLeft, AUnicodeCharRight: integer): TFreeTypeKerning;
|
|
const
|
|
UpperCaseKerningLeft = 'FPTVWY';
|
|
UpperCaseKerningRight = 'TVWY';
|
|
LowerCaseKerningLeftA = 'bcehmnops';
|
|
LowerCaseKerningRightA = 'cdegoqs';
|
|
LowerCaseKerningLeftU = 'gkqrvwxyz';
|
|
LowerCaseKerningRightU = 'mnprvwxyz';
|
|
LowerCaseKerningLeftACircumflex = 'ĉêôŝ';
|
|
LowerCaseKerningRightACircumflex = 'ĉêĝôŝ';
|
|
LowerCaseKerningLeftUCircumflex = 'ĝŵŷẑ';
|
|
LowerCaseKerningRightUCircumflex = 'ŵŷẑ';
|
|
LowerCaseKerningLeftADiaresis = 'ëö';
|
|
LowerCaseKerningRightADiaresis = 'ëö';
|
|
LowerCaseKerningLeftUDiaresis = 'ẅẍÿ';
|
|
LowerCaseKerningRightUDiaresis = 'ẅẍÿ';
|
|
LowerCaseKerningLeftAAcute = 'ćéḿńóṕś';
|
|
LowerCaseKerningRightAAcute = 'ćéǵóś';
|
|
LowerCaseKerningLeftUAcute = 'ǵŕẃýź';
|
|
LowerCaseKerningRightUAcute = 'ḿńṕŕẃýź';
|
|
LowerCaseKerningLeftAGrave = 'èǹò';
|
|
LowerCaseKerningRightAGrave = 'èò';
|
|
LowerCaseKerningLeftUGrave = 'ẁỳ';
|
|
LowerCaseKerningRightUGrave = 'ǹẁỳ';
|
|
type
|
|
TKerningFallbackInfo = record
|
|
u: integer; //composed charcode
|
|
fb: integer; //fallback code
|
|
end;
|
|
const
|
|
KerningFallbackInfo : array[0..195] of TKerningFallbackInfo = (
|
|
(u:$C0; fb:$41), (u:$C1; fb:$41), (u:$C2; fb:$41), (u:$C3; fb:$41), (u:$C4; fb:$41),
|
|
(u:$C5; fb:$41), (u:$C7; fb:$43), (u:$C8; fb:$45), (u:$C9; fb:$45), (u:$CA; fb:$45),
|
|
(u:$CB; fb:$45), (u:$CC; fb:$49), (u:$CD; fb:$49), (u:$CE; fb:$49), (u:$CF; fb:$49),
|
|
(u:$D1; fb:$4E), (u:$D2; fb:$4F), (u:$D3; fb:$4F), (u:$D4; fb:$4F), (u:$D5; fb:$4F),
|
|
(u:$D6; fb:$4F), (u:$D9; fb:$55), (u:$DA; fb:$55), (u:$DB; fb:$55), (u:$DC; fb:$55),
|
|
(u:$DD; fb:$59), (u:$100; fb:$41), (u:$102; fb:$41), (u:$104; fb:$41),
|
|
(u:$106; fb:$43), (u:$108; fb:$43), (u:$10A; fb:$43), (u:$10C; fb:$43),
|
|
(u:$10E; fb:$44), (u:$112; fb:$45), (u:$114; fb:$45), (u:$116; fb:$45),
|
|
(u:$118; fb:$45), (u:$11A; fb:$45), (u:$11C; fb:$47), (u:$11E; fb:$47),
|
|
(u:$120; fb:$47), (u:$122; fb:$47), (u:$124; fb:$48), (u:$128; fb:$49),
|
|
(u:$12A; fb:$49), (u:$12C; fb:$49), (u:$12E; fb:$49), (u:$130; fb:$49),
|
|
(u:$134; fb:$4A), (u:$136; fb:$4B), (u:$139; fb:$4C), (u:$13B; fb:$4C),
|
|
(u:$13D; fb:$4C), (u:$143; fb:$4E), (u:$145; fb:$4E), (u:$147; fb:$4E),
|
|
(u:$14C; fb:$4F), (u:$14E; fb:$4F), (u:$150; fb:$4F), (u:$154; fb:$52),
|
|
(u:$156; fb:$52), (u:$158; fb:$52), (u:$15A; fb:$53), (u:$15C; fb:$53),
|
|
(u:$15E; fb:$53), (u:$160; fb:$53), (u:$162; fb:$54), (u:$164; fb:$54),
|
|
(u:$168; fb:$55), (u:$16A; fb:$55), (u:$16C; fb:$55), (u:$16E; fb:$55),
|
|
(u:$170; fb:$55), (u:$172; fb:$55), (u:$174; fb:$57), (u:$176; fb:$59),
|
|
(u:$178; fb:$59), (u:$179; fb:$5A), (u:$17B; fb:$5A), (u:$17D; fb:$5A),
|
|
(u:$1CD; fb:$41), (u:$1CF; fb:$49), (u:$1D1; fb:$4F), (u:$1D3; fb:$55),
|
|
(u:$1E2; fb:$C6), (u:$1E6; fb:$47), (u:$1E8; fb:$4B), (u:$1EA; fb:$4F),
|
|
(u:$1F4; fb:$47), (u:$1F8; fb:$4E), (u:$1FC; fb:$C6), (u:$200; fb:$41),
|
|
(u:$202; fb:$41), (u:$204; fb:$45), (u:$206; fb:$45), (u:$208; fb:$49),
|
|
(u:$20A; fb:$49), (u:$20C; fb:$4F), (u:$20E; fb:$4F), (u:$210; fb:$52),
|
|
(u:$212; fb:$52), (u:$214; fb:$55), (u:$216; fb:$55), (u:$218; fb:$53),
|
|
(u:$21A; fb:$54), (u:$21E; fb:$48), (u:$226; fb:$41), (u:$228; fb:$45),
|
|
(u:$22E; fb:$4F), (u:$232; fb:$59), (u:$38F; fb:$3A9), (u:$403; fb:$413),
|
|
(u:$476; fb:$474), (u:$4EA; fb:$4E8), (u:$1E00; fb:$41), (u:$1E02; fb:$42),
|
|
(u:$1E04; fb:$42), (u:$1E06; fb:$42), (u:$1E08; fb:$C7), (u:$1E0A; fb:$44),
|
|
(u:$1E0C; fb:$44), (u:$1E0E; fb:$44), (u:$1E10; fb:$44), (u:$1E12; fb:$44),
|
|
(u:$1E18; fb:$45), (u:$1E1A; fb:$45), (u:$1E1E; fb:$46), (u:$1E20; fb:$47),
|
|
(u:$1E22; fb:$48), (u:$1E24; fb:$48), (u:$1E26; fb:$48), (u:$1E28; fb:$48),
|
|
(u:$1E2A; fb:$48), (u:$1E2C; fb:$49), (u:$1E30; fb:$4B), (u:$1E32; fb:$4B),
|
|
(u:$1E34; fb:$4B), (u:$1E36; fb:$4C), (u:$1E3A; fb:$4C), (u:$1E3C; fb:$4C),
|
|
(u:$1E3E; fb:$4D), (u:$1E40; fb:$4D), (u:$1E42; fb:$4D), (u:$1E44; fb:$4E),
|
|
(u:$1E46; fb:$4E), (u:$1E48; fb:$4E), (u:$1E4A; fb:$4E), (u:$1E54; fb:$50),
|
|
(u:$1E56; fb:$50), (u:$1E58; fb:$52), (u:$1E5A; fb:$52), (u:$1E5E; fb:$52),
|
|
(u:$1E60; fb:$53), (u:$1E62; fb:$53), (u:$1E6A; fb:$54), (u:$1E6C; fb:$54),
|
|
(u:$1E6E; fb:$54), (u:$1E70; fb:$54), (u:$1E72; fb:$55), (u:$1E74; fb:$55),
|
|
(u:$1E76; fb:$55), (u:$1E7C; fb:$56), (u:$1E7E; fb:$56), (u:$1E80; fb:$57),
|
|
(u:$1E82; fb:$57), (u:$1E84; fb:$57), (u:$1E86; fb:$57), (u:$1E88; fb:$57),
|
|
(u:$1E8A; fb:$58), (u:$1E8C; fb:$58), (u:$1E8E; fb:$59), (u:$1E90; fb:$5A),
|
|
(u:$1E92; fb:$5A), (u:$1E94; fb:$5A), (u:$1EA0; fb:$41), (u:$1EA2; fb:$41),
|
|
(u:$1EB8; fb:$45), (u:$1EBA; fb:$45), (u:$1EBC; fb:$45), (u:$1EC8; fb:$49),
|
|
(u:$1ECA; fb:$49), (u:$1ECC; fb:$4F), (u:$1ECE; fb:$4F), (u:$1EE4; fb:$55),
|
|
(u:$1EE6; fb:$55), (u:$1EF2; fb:$59), (u:$1EF4; fb:$59), (u:$1EF6; fb:$59),
|
|
(u:$1EF8; fb:$59), (u:$1F68; fb:$3A9), (u:$1F69; fb:$3A9), (u:$1FFA; fb:$3A9),
|
|
(u:$1FFC; fb:$3A9), (u:$2126; fb:$3A9), (u:$212A; fb:$4B));
|
|
|
|
function FindFallback(var ACode: integer): boolean;
|
|
var
|
|
minIdx, maxIdx, midIdx: Integer;
|
|
begin
|
|
minIdx := low(KerningFallbackInfo);
|
|
maxIdx := high(KerningFallbackInfo);
|
|
while minIdx < maxIdx do
|
|
begin
|
|
midIdx := (minIdx+maxIdx) shr 1;
|
|
if ACode > KerningFallbackInfo[midIdx].u then
|
|
minIdx := midIdx+1
|
|
else
|
|
maxIdx := midIdx;
|
|
end;
|
|
if KerningFallbackInfo[minIdx].u = ACode then
|
|
begin
|
|
ACode := KerningFallbackInfo[minIdx].fb;
|
|
if ACode = $C7 {C WITH CEDILLA} then ACode := ord('C');
|
|
result := true;
|
|
end
|
|
else result := false;
|
|
end;
|
|
var
|
|
glyphLeft, glyphRight: integer;
|
|
isFallback: Boolean;
|
|
leftUTF8, rightUTF8: String;
|
|
begin
|
|
glyphLeft := CharIndex[AUnicodeCharLeft];
|
|
glyphRight := CharIndex[AUnicodeCharRight];
|
|
result := GetGlyphKerning(glyphLeft, glyphRight);
|
|
if not result.Found and KerningFallbackEnabled then
|
|
begin
|
|
//try to find glyphs without accents
|
|
isFallback := false;
|
|
if FindFallback(AUnicodeCharLeft) then
|
|
begin
|
|
glyphLeft := CharIndex[AUnicodeCharLeft];
|
|
isFallback := true;
|
|
end;
|
|
if FindFallback(AUnicodeCharRight) then
|
|
begin
|
|
glyphRight := CharIndex[AUnicodeCharRight];
|
|
isFallback := true;
|
|
end;
|
|
if isFallback then
|
|
begin
|
|
result := GetGlyphKerning(glyphLeft, glyphRight);
|
|
if result.Found then exit;
|
|
end;
|
|
|
|
//try to find equivalence for kernings that were not forseen by the font (ex: AE, Vs)
|
|
if AUnicodeCharRight = $C6 {AE} then
|
|
begin
|
|
AUnicodeCharRight := ord('A');
|
|
glyphRight := CharIndex[AUnicodeCharRight];
|
|
result := GetGlyphKerning(glyphLeft, glyphRight);
|
|
if result.Found then exit;
|
|
end else
|
|
if AUnicodeCharRight = $152 {OE} then
|
|
begin
|
|
AUnicodeCharRight := ord('O');
|
|
glyphRight := CharIndex[AUnicodeCharRight];
|
|
result := GetGlyphKerning(glyphLeft, glyphRight);
|
|
if result.Found then exit;
|
|
end;
|
|
|
|
if (AUnicodeCharLeft < 128) and (AUnicodeCharRight < 128) then
|
|
begin
|
|
if (pos(chr(AUnicodeCharLeft), UpperCaseKerningLeft) <> 0) and
|
|
(pos(chr(AUnicodeCharRight), LowerCaseKerningRightA) <> 0) then
|
|
exit(GetCharKerning(AUnicodeCharLeft, ord('a')));
|
|
|
|
if (pos(chr(AUnicodeCharLeft), LowerCaseKerningLeftA) <> 0) and
|
|
(pos(chr(AUnicodeCharRight), UpperCaseKerningRight) <> 0) then
|
|
exit(GetCharKerning(ord('a'), AUnicodeCharRight));
|
|
|
|
if (pos(chr(AUnicodeCharLeft), UpperCaseKerningLeft) <> 0) and
|
|
(pos(chr(AUnicodeCharRight), LowerCaseKerningRightU) <> 0) then
|
|
exit(GetCharKerning(AUnicodeCharLeft, ord('u')));
|
|
|
|
if (pos(chr(AUnicodeCharLeft), LowerCaseKerningLeftU) <> 0) and
|
|
(pos(chr(AUnicodeCharRight), UpperCaseKerningRight) <> 0) then
|
|
exit(GetCharKerning(ord('u'), AUnicodeCharRight));
|
|
end else
|
|
begin
|
|
leftUTF8 := UnicodeToUTF8(AUnicodeCharLeft);
|
|
rightUTF8 := UnicodeToUTF8(AUnicodeCharRight);
|
|
|
|
if (pos(leftUTF8, UpperCaseKerningLeft) <> 0) and
|
|
(pos(rightUTF8, LowerCaseKerningRightACircumflex) <> 0) then
|
|
exit(GetCharKerning(AUnicodeCharLeft, $E2));
|
|
|
|
if (pos(leftUTF8, LowerCaseKerningLeftACircumflex) <> 0) and
|
|
(pos(rightUTF8, UpperCaseKerningRight) <> 0) then
|
|
exit(GetCharKerning($E2, AUnicodeCharRight));
|
|
|
|
if (pos(leftUTF8, UpperCaseKerningLeft) <> 0) and
|
|
(pos(rightUTF8, LowerCaseKerningRightUCircumflex) <> 0) then
|
|
exit(GetCharKerning(AUnicodeCharLeft, $FB));
|
|
|
|
if (pos(leftUTF8, LowerCaseKerningLeftUCircumflex) <> 0) and
|
|
(pos(rightUTF8, UpperCaseKerningRight) <> 0) then
|
|
exit(GetCharKerning($FB, AUnicodeCharRight));
|
|
|
|
|
|
if (pos(leftUTF8, UpperCaseKerningLeft) <> 0) and
|
|
(pos(rightUTF8, LowerCaseKerningRightADiaresis) <> 0) then
|
|
exit(GetCharKerning(AUnicodeCharLeft, $E4));
|
|
|
|
if (pos(leftUTF8, LowerCaseKerningLeftADiaresis) <> 0) and
|
|
(pos(rightUTF8, UpperCaseKerningRight) <> 0) then
|
|
exit(GetCharKerning($E4, AUnicodeCharRight));
|
|
|
|
if (pos(leftUTF8, UpperCaseKerningLeft) <> 0) and
|
|
(pos(rightUTF8, LowerCaseKerningRightUDiaresis) <> 0) then
|
|
exit(GetCharKerning(AUnicodeCharLeft, $FC));
|
|
|
|
if (pos(leftUTF8, LowerCaseKerningLeftUDiaresis) <> 0) and
|
|
(pos(rightUTF8, UpperCaseKerningRight) <> 0) then
|
|
exit(GetCharKerning($FC, AUnicodeCharRight));
|
|
|
|
|
|
if (pos(leftUTF8, UpperCaseKerningLeft) <> 0) and
|
|
(pos(rightUTF8, LowerCaseKerningRightAAcute) <> 0) then
|
|
exit(GetCharKerning(AUnicodeCharLeft, $E1));
|
|
|
|
if (pos(leftUTF8, LowerCaseKerningLeftAAcute) <> 0) and
|
|
(pos(rightUTF8, UpperCaseKerningRight) <> 0) then
|
|
exit(GetCharKerning($E1, AUnicodeCharRight));
|
|
|
|
if (pos(leftUTF8, UpperCaseKerningLeft) <> 0) and
|
|
(pos(rightUTF8, LowerCaseKerningRightUAcute) <> 0) then
|
|
exit(GetCharKerning(AUnicodeCharLeft, $FA));
|
|
|
|
if (pos(leftUTF8, LowerCaseKerningLeftUAcute) <> 0) and
|
|
(pos(rightUTF8, UpperCaseKerningRight) <> 0) then
|
|
exit(GetCharKerning($FA, AUnicodeCharRight));
|
|
|
|
|
|
if (pos(leftUTF8, UpperCaseKerningLeft) <> 0) and
|
|
(pos(rightUTF8, LowerCaseKerningRightAGrave) <> 0) then
|
|
exit(GetCharKerning(AUnicodeCharLeft, $E0));
|
|
|
|
if (pos(leftUTF8, LowerCaseKerningLeftAGrave) <> 0) and
|
|
(pos(rightUTF8, UpperCaseKerningRight) <> 0) then
|
|
exit(GetCharKerning($E0, AUnicodeCharRight));
|
|
|
|
if (pos(leftUTF8, UpperCaseKerningLeft) <> 0) and
|
|
(pos(rightUTF8, LowerCaseKerningRightUGrave) <> 0) then
|
|
exit(GetCharKerning(AUnicodeCharLeft, $F9));
|
|
|
|
if (pos(leftUTF8, LowerCaseKerningLeftUGrave) <> 0) and
|
|
(pos(rightUTF8, UpperCaseKerningRight) <> 0) then
|
|
exit(GetCharKerning($F9, AUnicodeCharRight));
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TFreeTypeFont.CheckInstance: boolean;
|
|
begin
|
|
result := CheckFace and FInstanceCreated;
|
|
end;
|
|
|
|
{ TFreeTypeGrayscaleMap }
|
|
|
|
procedure TFreeTypeGrayscaleMap.Init(AWidth, AHeight: integer);
|
|
begin
|
|
map.Width := AWidth;
|
|
map.Rows := AHeight;
|
|
map.Cols:= (AWidth+3) and not 3;
|
|
map.flow:= TT_Flow_Down;
|
|
map.Size:= map.Rows*map.Cols;
|
|
getmem(map.Buffer,map.Size);
|
|
Clear;
|
|
RenderQuality := grqHighQuality;
|
|
end;
|
|
|
|
function TFreeTypeGrayscaleMap.RenderGlyph(glyph: TFreeTypeGlyph; x, y: single): boolean;
|
|
var mono: TFreeTypeMonochromeMap;
|
|
psrc,pdest: pbyte;
|
|
xb,yb,tx: integer;
|
|
curBit: byte;
|
|
begin
|
|
case RenderQuality of
|
|
grqMonochrome:
|
|
begin
|
|
tx := Width;
|
|
mono := TFreeTypeMonochromeMap.Create(FRasterizer, tx,Height);
|
|
result := mono.RenderGlyph(glyph,x,y);
|
|
if result then
|
|
begin
|
|
for yb := mono.Height-1 downto 0 do
|
|
begin
|
|
psrc := mono.ScanLine[yb];
|
|
pdest := self.ScanLine[yb];
|
|
curBit := $80;
|
|
for xb := tx-1 downto 0 do
|
|
begin
|
|
if psrc^ and curBit <> 0 then
|
|
pdest^ := $ff;
|
|
curBit := curBit shr 1;
|
|
if curBit = 0 then
|
|
begin
|
|
curBit := $80;
|
|
inc(psrc);
|
|
end;
|
|
inc(pdest);
|
|
end;
|
|
end;
|
|
end;
|
|
mono.Free;
|
|
end;
|
|
grqLowQuality:
|
|
begin
|
|
FRasterizer.Set_Raster_Palette(RegularGray5);
|
|
result := TT_Get_Glyph_Pixmap(glyph.data, map, round(x*64), round((height-y)*64), FRasterizer) = TT_Err_Ok;
|
|
end;
|
|
grqHighQuality:
|
|
begin
|
|
result := TT_Get_Glyph_Pixmap_HQ(glyph.data, map, round(x*64), round((height-y)*64), FRasterizer) = TT_Err_Ok;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TFreeTypeGrayscaleMap.ScanMoveTo(x, y: integer);
|
|
begin
|
|
ScanPtrStart := pbyte(ScanLine[y]);
|
|
ScanX := x mod Width;
|
|
if ScanX < 0 then inc(ScanX,Width);
|
|
end;
|
|
|
|
function TFreeTypeGrayscaleMap.ScanNextPixel: byte;
|
|
begin
|
|
if ScanPtrStart = nil then
|
|
result := 0
|
|
else
|
|
begin
|
|
result := (ScanPtrStart+ScanX)^;
|
|
inc(ScanX);
|
|
if ScanX = map.Width then ScanX := 0;
|
|
end;
|
|
end;
|
|
|
|
function TFreeTypeGrayscaleMap.GetPixel(x, y: integer): byte;
|
|
begin
|
|
if (x < 0) or (x>= width) or (y <0) or (y >= height) then
|
|
result := 0
|
|
else
|
|
result := (pbyte(map.Buffer) + y*map.Cols + x)^;
|
|
end;
|
|
|
|
procedure TFreeTypeGrayscaleMap.SetPixel(x, y: integer; value: byte);
|
|
begin
|
|
if (x < 0) or (x>= width) or (y <0) or (y >= height) then
|
|
exit
|
|
else
|
|
(pbyte(map.Buffer) + y*map.Cols + x)^ := value;
|
|
end;
|
|
|
|
procedure TFreeTypeGrayscaleMap.XorPixel(x, y: integer; value: byte);
|
|
var p : pbyte;
|
|
begin
|
|
if (x < 0) or (x>= width) or (y <0) or (y >= height) then
|
|
exit
|
|
else
|
|
begin
|
|
p := (pbyte(map.Buffer) + y*map.Cols + x);
|
|
p^ := p^ xor value;
|
|
end;
|
|
end;
|
|
|
|
{ TFreeTypeRasterMap }
|
|
|
|
function TFreeTypeRasterMap.GetHeight: integer;
|
|
begin
|
|
result := map.Rows;
|
|
end;
|
|
|
|
function TFreeTypeRasterMap.GetWidth: integer;
|
|
begin
|
|
result := map.Width;
|
|
end;
|
|
|
|
function TFreeTypeRasterMap.GetScanLine(y: integer): pointer;
|
|
begin
|
|
if (y <0) or (y >= height) then
|
|
result := nil
|
|
else
|
|
Result:= pointer(pbyte(map.Buffer) + y*map.Cols);
|
|
end;
|
|
|
|
constructor TFreeTypeRasterMap.Create(AWidth, AHeight: integer);
|
|
begin
|
|
FRasterizer := TTGetDefaultRasterizer;
|
|
Init(AWidth,AHeight);
|
|
end;
|
|
|
|
constructor TFreeTypeRasterMap.Create(ARasterizer: TFreeTypeRasterizer; AWidth,
|
|
AHeight: integer);
|
|
begin
|
|
FRasterizer := ARasterizer;
|
|
Init(AWidth,AHeight);
|
|
end;
|
|
|
|
procedure TFreeTypeRasterMap.Clear;
|
|
begin
|
|
fillchar(map.Buffer^, map.Size, 0);
|
|
end;
|
|
|
|
procedure TFreeTypeRasterMap.Fill;
|
|
begin
|
|
fillchar(map.Buffer^, map.Size, $ff);
|
|
end;
|
|
|
|
destructor TFreeTypeRasterMap.Destroy;
|
|
begin
|
|
freemem(map.Buffer);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
{ TFreeTypeMonochromeMap }
|
|
|
|
function TFreeTypeMonochromeMap.RenderGlyph(glyph: TFreeTypeGlyph; x,y: single): boolean;
|
|
begin
|
|
result := TT_Get_Glyph_Bitmap(glyph.data, map, round(x*64), round((height-y)*64), FRasterizer) = TT_Err_Ok;
|
|
end;
|
|
|
|
procedure TFreeTypeMonochromeMap.ScanMoveTo(x, y: integer);
|
|
begin
|
|
ScanPtrStart := pbyte(ScanLine[y]);
|
|
ScanX := x mod Width;
|
|
if ScanX < 0 then inc(ScanX,Width);
|
|
|
|
if ScanPtrStart <> nil then
|
|
begin
|
|
ScanPtrCur := ScanPtrStart + (ScanX shr 3);
|
|
ScanBit := $80 shr (ScanX and 7);
|
|
end else
|
|
begin
|
|
ScanPtrCur := nil;
|
|
ScanBit := 0;
|
|
end;
|
|
end;
|
|
|
|
function TFreeTypeMonochromeMap.ScanNextPixel: boolean;
|
|
begin
|
|
if ScanPtrCur = nil then
|
|
result := false
|
|
else
|
|
begin
|
|
result := (pbyte(ScanPtrCur)^ and ScanBit) <> 0;
|
|
inc(ScanX);
|
|
if ScanX = map.Width then
|
|
begin
|
|
ScanX := 0;
|
|
ScanBit := $80;
|
|
ScanPtrCur := ScanPtrStart;
|
|
end else
|
|
begin
|
|
ScanBit := ScanBit shr 1;
|
|
if ScanBit = 0 then
|
|
begin
|
|
ScanBit := $80;
|
|
inc(ScanPtrCur);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TFreeTypeMonochromeMap.GetPixel(x, y: integer): boolean;
|
|
begin
|
|
if (x < 0) or (x>= width) or (y <0) or (y >= height) then
|
|
result := false
|
|
else
|
|
result := (pbyte(map.Buffer) + y*map.Cols + (x shr 3))^ and ($80 shr (x and 7)) <> 0;
|
|
end;
|
|
|
|
procedure TFreeTypeMonochromeMap.SetPixel(x, y: integer; value: boolean);
|
|
var p: pbyte;
|
|
begin
|
|
if (x < 0) or (x>= width) or (y <0) or (y >= height) then
|
|
exit
|
|
else
|
|
begin
|
|
p := pbyte(map.Buffer) + y*map.Cols + (x shr 3);
|
|
if not value then
|
|
p^ := p^ and not ($80 shr (x and 7))
|
|
else
|
|
p^ := p^ or ($80 shr (x and 7));
|
|
end;
|
|
end;
|
|
|
|
function TFreeTypeMonochromeMap.GetPixelsInRect(x, y, x2, y2: integer): integer;
|
|
var yb: integer;
|
|
begin
|
|
result := 0;
|
|
|
|
if x < 0 then x := 0;
|
|
if x2 > width then x2 := width;
|
|
if x2 <= x then exit;
|
|
|
|
if y < 0 then y := 0;
|
|
if y2 > height then y2 := height;
|
|
for yb := y to y2-1 do
|
|
result += GetPixelsInHorizlineNoBoundsChecking(x,yb,x2-1);
|
|
end;
|
|
|
|
function TFreeTypeMonochromeMap.GetPixelsInHorizline(x, y, x2: integer): integer;
|
|
begin
|
|
if x < 0 then x := 0;
|
|
if x2 >= width then x2 := width-1;
|
|
if x2 <= x then
|
|
begin
|
|
result := 0;
|
|
exit;
|
|
end;
|
|
if (y < 0) or (y >= height) then
|
|
begin
|
|
result := 0;
|
|
exit;
|
|
end;
|
|
|
|
result := GetPixelsInHorizlineNoBoundsChecking(x,y,x2);
|
|
end;
|
|
|
|
function TFreeTypeMonochromeMap.GetPixelsInHorizlineNoBoundsChecking(x, y, x2: integer
|
|
): integer;
|
|
var p: pbyte;
|
|
ix,ix2: integer;
|
|
begin
|
|
result := 0;
|
|
ix := x shr 3;
|
|
ix2 := x2 shr 3;
|
|
p := pbyte(map.Buffer) + y*map.Cols + ix;
|
|
if ix2 > ix then
|
|
begin
|
|
result += BitCountTable[ p^ and ($ff shr (x and 7)) ];
|
|
inc(p^);
|
|
inc(ix);
|
|
while (ix2 > ix) do
|
|
begin
|
|
result += BitCountTable[p^];
|
|
inc(ix);
|
|
inc(p^);
|
|
end;
|
|
result += BitCountTable[ p^ and ($ff shl (x2 and 7 xor 7)) ];
|
|
end else
|
|
result += BitCountTable[ p^ and ($ff shr (x and 7)) and ($ff shl (x2 and 7 xor 7))];
|
|
end;
|
|
|
|
procedure TFreeTypeMonochromeMap.Init(AWidth, AHeight: integer);
|
|
begin
|
|
map.Width := AWidth;
|
|
map.Rows := AHeight;
|
|
map.Cols:= (AWidth+7) shr 3;
|
|
map.flow:= TT_Flow_Down;
|
|
map.Size:= map.Rows*map.Cols;
|
|
getmem(map.Buffer,map.Size);
|
|
Clear;
|
|
end;
|
|
|
|
procedure TFreeTypeMonochromeMap.TogglePixel(x, y: integer);
|
|
var p: pbyte;
|
|
begin
|
|
if (x < 0) or (x>= width) or (y <0) or (y >= height) then
|
|
exit
|
|
else
|
|
begin
|
|
p := pbyte(map.Buffer) + y*map.Cols + (x shr 3);
|
|
p^ := p^ xor ($80 shr (x and 7));
|
|
end;
|
|
end;
|
|
|
|
procedure InitTables;
|
|
var i: integer;
|
|
begin
|
|
for i := 0 to 255 do
|
|
begin
|
|
BitCountTable[i] := (i and 1) + (i shr 1 and 1) + (i shr 2 and 1) + (i shr 3 and 1) +
|
|
(i shr 4 and 1) + (i shr 5 and 1) + (i shr 6 and 1) + (i shr 7 and 1);
|
|
end;
|
|
|
|
RegularGray5[0] := 0;
|
|
RegularGray5[1] := $60;
|
|
RegularGray5[2] := $a0;
|
|
RegularGray5[3] := $d0;
|
|
RegularGray5[4] := $ff;
|
|
end;
|
|
|
|
initialization
|
|
|
|
FreeTypeInitialized := false;
|
|
FreeTypeCannotInitialize := false;
|
|
InitTables;
|
|
|
|
finalization
|
|
|
|
if FreeTypeInitialized then
|
|
begin
|
|
TT_Done_FreeType;
|
|
FreeTypeInitialized := false;
|
|
end;
|
|
|
|
end.
|
|
|