From 678cc132f942ff4d84a803550eedf96acc543bca Mon Sep 17 00:00:00 2001 From: tobigun Date: Sun, 23 May 2010 09:07:15 +0000 Subject: update to trunk rev. 2391 git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/branches/experimental@2401 b956fd51-792f-4845-bead-9b4dfca2ff2c --- cmake/src/base/UFont.pas | 849 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 593 insertions(+), 256 deletions(-) (limited to 'cmake/src/base/UFont.pas') diff --git a/cmake/src/base/UFont.pas b/cmake/src/base/UFont.pas index a72bca21..49a19a1a 100644 --- a/cmake/src/base/UFont.pas +++ b/cmake/src/base/UFont.pas @@ -41,18 +41,23 @@ interface {$DEFINE BITMAP_FONT} {$ENDIF} +// Enables the Freetype font cache +{$DEFINE ENABLE_FT_FACE_CACHE} + uses FreeType, gl, glext, glu, sdl, + Math, + Classes, + SysUtils, + UUnicodeUtils, {$IFDEF BITMAP_FONT} UTexture, {$ENDIF} - Math, - Classes, - SysUtils; + UPath; type @@ -60,7 +65,7 @@ type TGLubyteArray = array[0 .. (MaxInt div SizeOf(GLubyte))-1] of GLubyte; TGLubyteDynArray = array of GLubyte; - TWideStringArray = array of WideString; + TUCS4StringArray = array of UCS4String; TGLColor = packed record case byte of @@ -86,6 +91,8 @@ type Width, Height: integer; end; + EFontError = class(Exception); + {** * Abstract base class representing a glyph. *} @@ -117,6 +124,7 @@ type procedure ResetIntern(); protected + fFilename: IPath; fStyle: TFontStyle; fUseKerning: boolean; fLineSpacing: single; // must be inited by subclass @@ -126,34 +134,34 @@ type {** * Splits lines in Text seperated by newline (char-code #13). - * @param Text UTF-8 encoded string - * @param Lines splitted WideString lines + * @param Text UCS-4 encoded string + * @param Lines splitted UCS4String lines *} - procedure SplitLines(const Text: UTF8String; var Lines: TWideStringArray); + procedure SplitLines(const Text: UCS4String; var Lines: TUCS4StringArray); {** - * Print an array of WideStrings. Each array-item is a line of text. + * Print an array of UCS4Strings. Each array-item is a line of text. * Lines of text are seperated by the line-spacing. * This is the base function for all text drawing. *} - procedure Print(const Text: TWideStringArray); overload; virtual; + procedure Print(const Text: TUCS4StringArray); overload; virtual; {** * Draws an underline. *} - procedure DrawUnderline(const Text: WideString); virtual; + procedure DrawUnderline(const Text: UCS4String); virtual; {** * Renders (one) line of text. *} - procedure Render(const Text: WideString); virtual; abstract; + procedure Render(const Text: UCS4String); virtual; abstract; {** * Returns the bounds of text-lines contained in Text. * @param(Advance if true the right bound is set to the advance instead * of the minimal right bound.) *} - function BBox(const Text: TWideStringArray; Advance: boolean): TBoundsDbl; overload; virtual; abstract; + function BBox(const Text: TUCS4StringArray; Advance: boolean): TBoundsDbl; overload; virtual; abstract; {** * Resets all user settings to default values. @@ -182,15 +190,17 @@ type property ReflectionPass: boolean read fReflectionPass write SetReflectionPass; public - constructor Create(); + constructor Create(const Filename: IPath); destructor Destroy(); override; {** * Prints a text. *} + procedure Print(const Text: UCS4String); overload; + {** UTF-16 version of @link(Print) } procedure Print(const Text: WideString); overload; {** UTF-8 version of @link(Print) } - procedure Print(const Text: string); overload; + procedure Print(const Text: UTF8String); overload; {** * Calculates the bounding box (width and height) around Text. @@ -203,10 +213,18 @@ type * bigger than the text's width as it additionally contains the advance * and glyph-spacing of the last character. *} + function BBox(const Text: UCS4String; Advance: boolean = true): TBoundsDbl; overload; + {** UTF-16 version of @link(BBox) } function BBox(const Text: WideString; Advance: boolean = true): TBoundsDbl; overload; {** UTF-8 version of @link(BBox) } function BBox(const Text: UTF8String; Advance: boolean = true): TBoundsDbl; overload; + {** + * Adds a new font that is used if the default font misses a glyph + * @raises EFontError if the fallback could not be initialized + *} + procedure AddFallback(const Filename: IPath); virtual; abstract; + {** Font height } property Height: single read GetHeight; {** Vertical distance from baseline to top of glyph } @@ -223,6 +241,8 @@ type property Style: TFontStyle read GetStyle write SetStyle; {** If set to true (default) kerning will be used if available } property UseKerning: boolean read GetUseKerning write SetUseKerning; + {** Filename } + property Filename: IPath read fFilename; end; const @@ -242,16 +262,16 @@ type procedure ResetIntern(); protected - fScale: single; //**< current height to base-font height ratio - fAspect: single; //**< width to height aspect + fScale: single; //**< current height to base-font height ratio + fStretch: single; //**< stretch factor for width (Width * fStretch) fBaseFont: TFont; //**< shortcut for fMipmapFonts[0] fUseMipmaps: boolean; //**< true if mipmap fonts are generated /// Mipmap fonts (size[level+1] = size[level]/2) fMipmapFonts: array[0..cMaxMipmapLevel] of TFont; - procedure Render(const Text: WideString); override; - procedure Print(const Text: TWideStringArray); override; - function BBox(const Text: TWideStringArray; Advance: boolean): TBoundsDbl; override; + procedure Render(const Text: UCS4String); override; + procedure Print(const Text: TUCS4StringArray); override; + function BBox(const Text: TUCS4StringArray; Advance: boolean): TBoundsDbl; override; {** * Callback called for creation of each mipmap font. @@ -280,8 +300,8 @@ type procedure SetHeight(Height: single); virtual; function GetHeight(): single; override; - procedure SetAspect(Aspect: single); virtual; - function GetAspect(): single; virtual; + procedure SetStretch(Stretch: single); virtual; + function GetStretch(): single; virtual; function GetAscender(): single; override; function GetDescender(): single; override; procedure SetLineSpacing(Spacing: single); override; @@ -316,13 +336,13 @@ type {** Font height } property Height: single read GetHeight write SetHeight; - {** Factor for font stretching (NewWidth = Width*Aspect), 1.0 by default } - property Aspect: single read GetAspect write SetAspect; + {** Factor for font stretching (NewWidth = Width*Stretch), 1.0 by default } + property Stretch: single read GetStretch write SetStretch; end; {** * Table for storage of max. 256 glyphs. - * Used for the second cache level. Indexed by the LSB of the WideChar + * Used for the second cache level. Indexed by the LSB of the UCS4Char * char-code. *} PGlyphTable = ^TGlyphTable; @@ -332,7 +352,7 @@ type * Cache for glyphs of a single font. * The cached glyphs are stored inside a hash-list. * Hashing is performed in two steps: - * 1. the least significant byte (LSB) of the WideChar character code + * 1. the least significant byte (LSB) of the UCS4Char character code * is removed (shr 8) and the result (we call it BaseCode here) looked up in * the hash-list. * 2. Each entry of the hash-list contains a table with max. 256 entries. @@ -359,22 +379,22 @@ type * Add glyph Glyph with char-code ch to the cache. * @returns @true on success, @false otherwise *} - function AddGlyph(ch: WideChar; const Glyph: TGlyph): boolean; + function AddGlyph(ch: UCS4Char; const Glyph: TGlyph): boolean; {** * Removes the glyph with char-code ch from the cache. *} - procedure DeleteGlyph(ch: WideChar); + procedure DeleteGlyph(ch: UCS4Char); {** * Removes the glyph with char-code ch from the cache. *} - function GetGlyph(ch: WideChar): TGlyph; + function GetGlyph(ch: UCS4Char): TGlyph; {** * Checks if a glyph with char-code ch is cached. *} - function HasGlyph(ch: WideChar): boolean; + function HasGlyph(ch: UCS4Char): boolean; {** * Remove and free all cached glyphs. If KeepBaseSet is set to @@ -408,16 +428,16 @@ type * Retrieves a cached glyph with char-code ch from cache. * If the glyph is not already cached, it is loaded with LoadGlyph(). *} - function GetGlyph(ch: WideChar): TGlyph; + function GetGlyph(ch: UCS4Char): TGlyph; {** * Callback to create (load) a glyph with char-code ch. * Implemented by subclasses. *} - function LoadGlyph(ch: WideChar): TGlyph; virtual; abstract; + function LoadGlyph(ch: UCS4Char): TGlyph; virtual; abstract; public - constructor Create(); + constructor Create(const Filename: IPath); destructor Destroy(); override; {** @@ -430,12 +450,56 @@ type TFTFont = class; + {** + * Freetype font face class. + *} + TFTFontFace = class + private + fFilename: IPath; //**< filename of the font-file + fFace: FT_Face; //**< Holds the height of the font + fFontUnitScale: TPositionDbl; //**< FT font-units to pixel ratio + fSize: integer; + + public + {** + * @raises EFontError if the glyph could not be initialized + *} + constructor Create(const Filename: IPath; Size: integer); + + destructor Destroy(); override; + + property Filename: IPath read fFilename; + property Data: FT_Face read fFace; + property FontUnitScale: TPositionDbl read fFontUnitScale; + property Size: integer read fSize; + end; + + {** + * Loading font faces with freetype is a slow process. + * Especially loading a font (e.g. fallback fonts) more than once is a waste + * of time. Just cache already loaded faces here. + *} + TFTFontFaceCache = class + private + fFaces: array of TFTFontFace; + fFacesRefCnt: array of integer; + public + {** + * @raises EFontError if the font could not be initialized + *} + function LoadFace(const Filename: IPath; Size: integer): TFTFontFace; + + procedure UnloadFace(Face: TFTFontFace); + end; + {** * Freetype glyph. * Each glyph stores a texture with the glyph's image. *} TFTGlyph = class(TGlyph) private + fCharCode: UCS4Char; //**< Char code + fFace: TFTFontFace; //**< Freetype face used for this glyph fCharIndex: FT_UInt; //**< Freetype specific char-index (<> char-code) fDisplayList: GLuint; //**< Display-list ID fTexture: GLuint; //**< Texture ID @@ -458,13 +522,13 @@ type * The bitmap must be 2* pixels wider and higher than the * original glyph's bitmap with the latter centered in it. *} - procedure Extrude(var TexBuffer: TGLubyteDynArray; Outset: single); + procedure StrokeBorder(var Glyph: FT_Glyph); {** * Creates an OpenGL texture (and display list) for the glyph. * The glyph's and bitmap's metrics are set correspondingly. * @param LoadFlags flags passed to FT_Load_Glyph() - * @raises Exception if the glyph could not be initialized + * @raises EFontError if the glyph could not be initialized *} procedure CreateTexture(LoadFlags: FT_Int32); @@ -477,7 +541,7 @@ type * Creates a glyph with char-code ch from font Font. * @param LoadFlags flags passed to FT_Load_Glyph() *} - constructor Create(Font: TFTFont; ch: WideChar; Outset: single; + constructor Create(Font: TFTFont; ch: UCS4Char; Outset: single; LoadFlags: FT_Int32); destructor Destroy(); override; @@ -488,29 +552,36 @@ type {** Freetype specific char-index (<> char-code) } property CharIndex: FT_UInt read fCharIndex; + + {** Freetype face used for this glyph } + property Face: TFTFontFace read fFace; end; + TFontPart = ( fpNone, fpInner, fpOutline ); + TFTFontFaceArray = array of TFTFontFace; + {** * Freetype font class. *} TFTFont = class(TCachedFont) private procedure ResetIntern(); + class function GetFaceCache(): TFTFontFaceCache; protected - fFilename: string; //**< filename of the font-file + fFace: TFTFontFace; //**< Default font face fSize: integer; //**< Font base size (in pixels) fOutset: single; //**< size of outset extrusion (in pixels) - fFace: FT_Face; //**< Holds the height of the font fLoadFlags: FT_Int32; //**< FT glpyh load-flags - fFontUnitScale: TPositionDbl; //**< FT font-units to pixel ratio fUseDisplayLists: boolean; //**< true: use display-lists, false: direct drawing + fPart: TFontPart; //**< indicates the part of an outline font + fFallbackFaces: TFTFontFaceArray; //**< available fallback faces, ordered by priority {** @seealso TCachedFont.LoadGlyph } - function LoadGlyph(ch: WideChar): TGlyph; override; + function LoadGlyph(ch: UCS4Char): TGlyph; override; - procedure Render(const Text: WideString); override; - function BBox(const Text: TWideStringArray; Advance: boolean): TBoundsDbl; override; + procedure Render(const Text: UCS4String); override; + function BBox(const Text: TUCS4StringArray; Advance: boolean): TBoundsDbl; override; function GetHeight(): single; override; function GetAscender(): single; override; @@ -518,17 +589,15 @@ type function GetUnderlinePosition(): single; override; function GetUnderlineThickness(): single; override; - property Face: FT_Face read fFace; - public {** * Creates a font of size Size (in pixels) from the file Filename. * If Outset (in pixels) is set to a value > 0 the glyphs will be extruded * at their borders. Use it for e.g. a bold effect. * @param LoadFlags flags passed to FT_Load_Glyph() - * @raises Exception if the font-file could not be loaded + * @raises EFontError if the font-file could not be loaded *} - constructor Create(const Filename: string; + constructor Create(const Filename: IPath; Size: integer; Outset: single = 0.0; LoadFlags: FT_Int32 = FT_LOAD_DEFAULT); @@ -539,11 +608,19 @@ type {** @seealso TFont.Reset } procedure Reset(); override; - + + procedure AddFallback(const Filename: IPath); override; + {** Size of the base font } property Size: integer read fSize; {** Outset size } property Outset: single read fOutset; + {** The part (inner/outline/none) this font represents in a composite font } + property Part: TFontPart read fPart write fPart; + {** Freetype face of this font } + property DefaultFace: TFTFontFace read fFace; + {** Available freetype fallback faces, ordered by priority } + property FallbackFaces: TFTFontFaceArray read fFallbackFaces; end; TFTScalableFont = class(TScalableFont) @@ -557,11 +634,27 @@ type * OutsetAmount is the ratio of the glyph extrusion. * The extrusion in pixels is Size*OutsetAmount * (0.0 -> no extrusion, 0.1 -> 10%). + * + * The memory size (in bytes) consumed by a scalable font + * - with UseMipmaps=false: + * mem = size^2 * #cached_glyphs + * - with UseMipmaps=true (all mipmap levels): + * mem = size^2 * #cached_glyphs * Sum[i=1..cMaxMipmapLevel](1/i^2) + * - with UseMipmaps=true (5 <= cMaxMipmapLevel <= 10): + * mem ~= size^2 * #cached_glyphs * 1.5 + * + * Examples (for 128 cached glyphs): + * - Size: 64 pixels: 768 KB (mipmapped) or 512 KB (non-mipmapped). + * - Size 128 pixels: 3 MB (mipmapped) or 2 MB (non-mipmapped) + * + * Note: once a glyph is cached there will *} - constructor Create(const Filename: string; + constructor Create(const Filename: IPath; Size: integer; OutsetAmount: single = 0.0; UseMipmaps: boolean = true); + procedure AddFallback(const Filename: IPath); override; + {** @seealso TGlyphCache.FlushCache } procedure FlushCache(KeepBaseSet: boolean); @@ -576,7 +669,6 @@ type *} TFTOutlineFont = class(TFont) private - fFilename: string; fSize: integer; fOutset: single; fInnerFont, fOutlineFont: TFTFont; @@ -585,9 +677,9 @@ type procedure ResetIntern(); protected - procedure DrawUnderline(const Text: WideString); override; - procedure Render(const Text: WideString); override; - function BBox(const Text: TWideStringArray; Advance: boolean): TBoundsDbl; override; + procedure DrawUnderline(const Text: UCS4String); override; + procedure Render(const Text: UCS4String); override; + function BBox(const Text: TUCS4StringArray; Advance: boolean): TBoundsDbl; override; function GetHeight(): single; override; function GetAscender(): single; override; @@ -603,7 +695,7 @@ type procedure SetReflectionPass(Enable: boolean); override; public - constructor Create(const Filename: string; + constructor Create(const Filename: IPath; Size: integer; Outset: single; LoadFlags: FT_Int32 = FT_LOAD_DEFAULT); destructor Destroy; override; @@ -618,6 +710,8 @@ type {** @seealso TGlyphCache.FlushCache } procedure FlushCache(KeepBaseSet: boolean); + procedure AddFallback(const Filename: IPath); override; + {** @seealso TFont.Reset } procedure Reset(); override; @@ -637,7 +731,7 @@ type function CreateMipmap(Level: integer; Scale: single): TFont; override; public - constructor Create(const Filename: string; + constructor Create(const Filename: IPath; Size: integer; OutsetAmount: single; UseMipmaps: boolean = true); @@ -647,6 +741,8 @@ type {** @seealso TGlyphCache.FlushCache } procedure FlushCache(KeepBaseSet: boolean); + procedure AddFallback(const Filename: IPath); override; + {** Outset size } property Outset: single read GetOutset; end; @@ -672,18 +768,18 @@ type procedure ResetIntern(); - procedure RenderChar(ch: WideChar; var AdvanceX: real); + procedure RenderChar(ch: UCS4Char; var AdvanceX: real); {** * Load font widths from an info file. * @param InfoFile the name of the info (.dat) file - * @raises Exception if the file is corrupted + * @raises EFontError if the file is corrupted *} - procedure LoadFontInfo(const InfoFile: string); + procedure LoadFontInfo(const InfoFile: IPath); protected - procedure Render(const Text: WideString); override; - function BBox(const Text: TWideStringArray; Advance: boolean): TBoundsDbl; override; + procedure Render(const Text: UCS4String); override; + function BBox(const Text: TUCS4StringArray; Advance: boolean): TBoundsDbl; override; function GetHeight(): single; override; function GetAscender(): single; override; @@ -699,7 +795,7 @@ type * (y-axis up) and from the lower edge of the glyphs bounding box) * @param(Ascender pixels from baseline to top of highest glyph) *} - constructor Create(const Filename: string; Outline: integer; + constructor Create(const Filename: IPath; Outline: integer; Baseline, Ascender, Descender: integer); destructor Destroy(); override; @@ -711,6 +807,8 @@ type {** @seealso TFont.Reset } procedure Reset(); override; + + procedure AddFallback(const Filename: IPath); override; end; {$ENDIF BITMAP_FONT} @@ -720,7 +818,7 @@ type {** * Returns a pointer to the freetype library singleton. * If non exists, freetype will be initialized. - * @raises Exception if initialization failed + * @raises EFontError if initialization failed *} class function GetLibrary(): FT_Library; class procedure FreeLibrary(); @@ -773,9 +871,10 @@ end; * TFont *} -constructor TFont.Create(); +constructor TFont.Create(const Filename: IPath); begin - inherited; + inherited Create(); + fFilename := Filename; ResetIntern(); end; @@ -801,37 +900,61 @@ begin ResetIntern(); end; -procedure TFont.SplitLines(const Text: UTF8String; var Lines: TWideStringArray); +procedure TFont.SplitLines(const Text: UCS4String; var Lines: TUCS4StringArray); var - LineList: TStringList; - LineIndex: integer; + CharIndex: integer; + LineStart: integer; + LineLength: integer; + EOT: boolean; // End-Of-Text begin - // split lines on newline (there is no WideString version of ExtractStrings) - LineList := TStringList.Create(); - ExtractStrings([#13], [], PChar(Text), LineList); + // split lines on newline + SetLength(Lines, 0); + EOT := false; + LineStart := 0; - // create an array of WideStrins from the UTF-8 string-list - SetLength(Lines, LineList.Count); - for LineIndex := 0 to LineList.Count-1 do - Lines[LineIndex] := UTF8Decode(LineList[LineIndex]); - LineList.Free(); + for CharIndex := 0 to High(Text) do + begin + // check for end of text (UCS4Strings are zero-terminated) + if (CharIndex = High(Text)) then + EOT := true; + + // check for newline (carriage return (#13)) or end of text + if (Text[CharIndex] = 13) or EOT then + begin + LineLength := CharIndex - LineStart; + // check if last character was a newline + if (EOT and (LineLength = 0)) then + Break; + + // copy line (even if LineLength is 0) + SetLength(Lines, Length(Lines)+1); + Lines[High(Lines)] := UCS4Copy(Text, LineStart, LineLength); + + LineStart := CharIndex+1; + end; + end; end; -function TFont.BBox(const Text: UTF8String; Advance: boolean): TBoundsDbl; +function TFont.BBox(const Text: UCS4String; Advance: boolean): TBoundsDbl; var - LineArray: TWideStringArray; + LineArray: TUCS4StringArray; begin SplitLines(Text, LineArray); Result := BBox(LineArray, Advance); SetLength(LineArray, 0); end; +function TFont.BBox(const Text: UTF8String; Advance: boolean): TBoundsDbl; +begin + Result := BBox(UTF8Decode(Text), Advance); +end; + function TFont.BBox(const Text: WideString; Advance: boolean): TBoundsDbl; begin - Result := BBox(UTF8Encode(Text), Advance); + Result := BBox(WideStringToUCS4String(Text), Advance); end; -procedure TFont.Print(const Text: TWideStringArray); +procedure TFont.Print(const Text: TUCS4StringArray); var LineIndex: integer; begin @@ -912,21 +1035,26 @@ begin glPopAttrib(); end; -procedure TFont.Print(const Text: string); +procedure TFont.Print(const Text: UCS4String); var - LineArray: TWideStringArray; + LineArray: TUCS4StringArray; begin SplitLines(Text, LineArray); Print(LineArray); SetLength(LineArray, 0); end; +procedure TFont.Print(const Text: UTF8String); +begin + Print(UTF8Decode(Text)); +end; + procedure TFont.Print(const Text: WideString); begin - Print(UTF8Encode(Text)); + Print(WideStringToUCS4String(Text)); end; -procedure TFont.DrawUnderline(const Text: WideString); +procedure TFont.DrawUnderline(const Text: UCS4String); var UnderlineY1, UnderlineY2: single; Bounds: TBoundsDbl; @@ -1001,7 +1129,7 @@ constructor TScalableFont.Create(Font: TFont; UseMipmaps: boolean); var MipmapLevel: integer; begin - inherited Create(); + inherited Create(Font.Filename); fBaseFont := Font; fMipmapFonts[0] := Font; @@ -1033,7 +1161,7 @@ end; procedure TScalableFont.ResetIntern(); begin fScale := 1.0; - fAspect := 1.0; + fStretch := 1.0; end; procedure TScalableFont.Reset(); @@ -1049,7 +1177,7 @@ end; {** * Returns the mipmap level to use with regard to the current projection - * and modelview matrix, font scale and aspect. + * and modelview matrix, font scale and stretch. * * Note: * - for Freetype fonts, hinting and grid-fitting must be disabled, otherwise @@ -1088,7 +1216,7 @@ var ModelMatrix, ProjMatrix: T16dArray; WinCoords: array[0..2, 0..2] of GLdouble; ViewPortArray: TViewPortArray; - Dist, Dist2: double; + Dist, Dist2, DistSum: double; WidthScale, HeightScale: double; const // width/height of square used for determining the scale @@ -1128,12 +1256,24 @@ begin // projected width ||(x1, y1) - (x2, y1)|| Dist := (WinCoords[0][0] - WinCoords[1][0]); Dist2 := (WinCoords[0][1] - WinCoords[1][1]); - WidthScale := cTestSize / Sqrt(Dist*Dist + Dist2*Dist2); + + WidthScale := 1; + DistSum := Dist*Dist + Dist2*Dist2; + if (DistSum > 0) then + begin + WidthScale := cTestSize / Sqrt(DistSum); + end; // projected height ||(x1, y1) - (x1, y2)|| Dist := (WinCoords[0][0] - WinCoords[2][0]); Dist2 := (WinCoords[0][1] - WinCoords[2][1]); - HeightScale := cTestSize / Sqrt(Dist*Dist + Dist2*Dist2); + + HeightScale := 1; + DistSum := Dist*Dist + Dist2*Dist2; + if (DistSum > 0) then + begin + HeightScale := cTestSize / Sqrt(DistSum); + end; //writeln(Format('Scale %f, %f', [WidthScale, HeightScale])); @@ -1194,12 +1334,12 @@ begin glScalef(MipmapScale, MipmapScale, 0); end; -procedure TScalableFont.Print(const Text: TWideStringArray); +procedure TScalableFont.Print(const Text: TUCS4StringArray); begin glPushMatrix(); // set scale and stretching - glScalef(fScale * fAspect, fScale, 0); + glScalef(fScale * fStretch, fScale, 0); // print text if (fUseMipmaps) then @@ -1210,16 +1350,16 @@ begin glPopMatrix(); end; -procedure TScalableFont.Render(const Text: WideString); +procedure TScalableFont.Render(const Text: UCS4String); begin Assert(false, 'Unused TScalableFont.Render() was called'); end; -function TScalableFont.BBox(const Text: TWideStringArray; Advance: boolean): TBoundsDbl; +function TScalableFont.BBox(const Text: TUCS4StringArray; Advance: boolean): TBoundsDbl; begin Result := fBaseFont.BBox(Text, Advance); - Result.Left := Result.Left * fScale * fAspect; - Result.Right := Result.Right * fScale * fAspect; + Result.Left := Result.Left * fScale * fStretch; + Result.Right := Result.Right * fScale * fStretch; Result.Top := Result.Top * fScale; Result.Bottom := Result.Bottom * fScale; end; @@ -1234,14 +1374,14 @@ begin Result := fBaseFont.GetHeight() * fScale; end; -procedure TScalableFont.SetAspect(Aspect: single); +procedure TScalableFont.SetStretch(Stretch: single); begin - fAspect := Aspect; + fStretch := Stretch; end; -function TScalableFont.GetAspect(): single; +function TScalableFont.GetStretch(): single; begin - Result := fAspect; + Result := fStretch; end; function TScalableFont.GetAscender(): single; @@ -1287,7 +1427,7 @@ var Level: integer; begin for Level := 0 to High(fMipmapFonts) do - if (fMipmapFonts[Level] <> nil) then + if ((fMipmapFonts[Level] <> nil) AND (GetMipmapScale(Level) > 0)) then fMipmapFonts[Level].SetReflectionSpacing(Spacing / GetMipmapScale(Level)); end; @@ -1334,9 +1474,9 @@ end; * TCachedFont *} -constructor TCachedFont.Create(); +constructor TCachedFont.Create(const Filename: IPath); begin - inherited; + inherited Create(Filename); fCache := TGlyphCache.Create(); end; @@ -1346,7 +1486,7 @@ begin inherited; end; -function TCachedFont.GetGlyph(ch: WideChar): TGlyph; +function TCachedFont.GetGlyph(ch: UCS4Char): TGlyph; begin Result := fCache.GetGlyph(ch); if (Result = nil) then @@ -1362,60 +1502,155 @@ begin fCache.FlushCache(KeepBaseSet); end; +{* + * TFTFontFaceCache + *} {* - * TFTFont + * TFTFontFace *} -constructor TFTFont.Create( - const Filename: string; - Size: integer; Outset: single; - LoadFlags: FT_Int32); -var - i: WideChar; +constructor TFTFontFace.Create(const Filename: IPath; Size: integer); begin inherited Create(); fFilename := Filename; fSize := Size; - fOutset := Outset; - fLoadFlags := LoadFlags; - fUseDisplayLists := true; // load font information - if (FT_New_Face(TFreeType.GetLibrary(), PChar(Filename), 0, fFace) <> 0) then - raise Exception.Create('FT_New_Face: Could not load font ''' + Filename + ''''); + if (FT_New_Face(TFreeType.GetLibrary(), PChar(Filename.ToNative), 0, fFace) <> 0) then + raise EFontError.Create('FT_New_Face: Could not load font ''' + Filename.ToNative + ''''); // support scalable fonts only if (not FT_IS_SCALABLE(fFace)) then - raise Exception.Create('Font is not scalable'); + raise EFontError.Create('Font is not scalable'); if (FT_Set_Pixel_Sizes(fFace, 0, Size) <> 0) then - raise Exception.Create('FT_Set_Pixel_Sizes failes'); + raise EFontError.Create('FT_Set_Pixel_Sizes failes'); // get scale factor for font-unit to pixel-size transformation fFontUnitScale.X := fFace.size.metrics.x_ppem / fFace.units_per_EM; fFontUnitScale.Y := fFace.size.metrics.y_ppem / fFace.units_per_EM; +end; + +destructor TFTFontFace.Destroy(); +begin + // free face data + FT_Done_Face(fFace); + inherited; +end; + + +{* + * TFTFontFaceCache + *} + +function TFTFontFaceCache.LoadFace(const Filename: IPath; Size: integer): TFTFontFace; +var + I: Integer; + Face: TFTFontFace; +begin + {$IFDEF ENABLE_FT_FACE_CACHE} + for I := 0 to High(fFaces) do + begin + Face := fFaces[I]; + // check if we have this file in our cache + if ((Face.Filename.Equals(Filename)) and (Face.Size = Size)) then + begin + // true -> return cached face and increment ref-count + Inc(fFacesRefCnt[I]); + Result := Face; + Exit; + end; + end; + {$ENDIF} + + // face not in cache -> load it + Face := TFTFontFace.Create(Filename, Size); + + // add face to cache + SetLength(fFaces, Length(fFaces)+1); + SetLength(fFacesRefCnt, Length(fFaces)+1); + fFaces[High(fFaces)] := Face; + fFacesRefCnt[High(fFaces)] := 1; + + Result := Face; +end; + +procedure TFTFontFaceCache.UnloadFace(Face: TFTFontFace); +var + I: Integer; +begin + for I := 0 to High(fFaces) do + begin + // search face in cache + if (fFaces[I] = Face) then + begin + // decrement ref-count and free face if ref-count is 0 + Dec(fFacesRefCnt[I]); + if (fFacesRefCnt[I] <= 0) then + fFaces[I].Free; + Exit; + end; + end; +end; + + +{* + * TFTFont + *} + +constructor TFTFont.Create( + const Filename: IPath; + Size: integer; Outset: single; + LoadFlags: FT_Int32); +var + ch: UCS4Char; +begin + inherited Create(Filename); + + fSize := Size; + fOutset := Outset; + fLoadFlags := LoadFlags; + fUseDisplayLists := true; + fPart := fpNone; + + fFace := GetFaceCache.LoadFace(Filename, Size); ResetIntern(); // pre-cache some commonly used glyphs (' ' - '~') - for i := #32 to #126 do - fCache.AddGlyph(i, TFTGlyph.Create(Self, i, Outset, LoadFlags)); + for ch := 32 to 126 do + fCache.AddGlyph(ch, TFTGlyph.Create(Self, ch, Outset, LoadFlags)); end; destructor TFTFont.Destroy(); +var + I: integer; begin - // free face - FT_Done_Face(fFace); + // free faces + GetFaceCache.UnloadFace(fFace); + for I := 0 to High(fFallbackFaces) do + GetFaceCache.UnloadFace(fFallbackFaces[I]); + inherited; end; +var + FontFaceCache: TFTFontFaceCache = nil; + +class function TFTFont.GetFaceCache(): TFTFontFaceCache; +begin + if (FontFaceCache = nil) then + FontFaceCache := TFTFontFaceCache.Create; + Result := FontFaceCache; +end; + procedure TFTFont.ResetIntern(); begin // Note: outset and non outset fonts use same spacing - fLineSpacing := fFace.height * fFontUnitScale.Y; - fReflectionSpacing := -2*fFace.descender * fFontUnitScale.Y; + fLineSpacing := fFace.Data.height * fFace.FontUnitScale.Y; + fReflectionSpacing := -2*fFace.Data.descender * fFace.FontUnitScale.Y; end; procedure TFTFont.Reset(); @@ -1424,15 +1659,24 @@ begin ResetIntern(); end; -function TFTFont.LoadGlyph(ch: WideChar): TGlyph; +procedure TFTFont.AddFallback(const Filename: IPath); +var + FontFace: TFTFontFace; +begin + FontFace := GetFaceCache.LoadFace(Filename, Size); + SetLength(fFallbackFaces, Length(fFallbackFaces) + 1); + fFallbackFaces[High(fFallbackFaces)] := FontFace; +end; + +function TFTFont.LoadGlyph(ch: UCS4Char): TGlyph; begin Result := TFTGlyph.Create(Self, ch, Outset, fLoadFlags); end; -function TFTFont.BBox(const Text: TWideStringArray; Advance: boolean): TBoundsDbl; +function TFTFont.BBox(const Text: TUCS4StringArray; Advance: boolean): TBoundsDbl; var Glyph, PrevGlyph: TFTGlyph; - TextLine: WideString; + TextLine: UCS4String; LineYOffset: single; LineIndex, CharIndex: integer; LineBounds: TBoundsDbl; @@ -1462,17 +1706,17 @@ begin LineBounds.Top := 0; // for each glyph image, compute its bounding box - for CharIndex := 1 to Length(TextLine) do + for CharIndex := 0 to LengthUCS4(TextLine)-1 do begin Glyph := TFTGlyph(GetGlyph(TextLine[CharIndex])); if (Glyph <> nil) then begin // get kerning - if (fUseKerning and FT_HAS_KERNING(fFace) and (PrevGlyph <> nil)) then + if (fUseKerning and FT_HAS_KERNING(fFace.Data) and (PrevGlyph <> nil)) then begin - FT_Get_Kerning(fFace, PrevGlyph.CharIndex, Glyph.CharIndex, + FT_Get_Kerning(fFace.Data, PrevGlyph.CharIndex, Glyph.CharIndex, FT_KERNING_UNSCALED, KernDelta); - LineBounds.Right := LineBounds.Right + KernDelta.x * fFontUnitScale.X; + LineBounds.Right := LineBounds.Right + KernDelta.x * fFace.FontUnitScale.X; end; // update left bound (must be done before right bound is updated) @@ -1480,9 +1724,9 @@ begin LineBounds.Left := LineBounds.Right + Glyph.Bounds.Left; // update right bound - if (CharIndex < Length(TextLine)) or // not the last character - (TextLine[CharIndex] = ' ') or // on space char (Bounds.Right = 0) - Advance then // or in advance mode + if (CharIndex < LengthUCS4(TextLine)-1) or // not the last character + (TextLine[CharIndex] = Ord(' ')) or // on space char (Bounds.Right = 0) + Advance then // or in advance mode begin // add advance and glyph spacing LineBounds.Right := LineBounds.Right + Glyph.Advance.x + GlyphSpacing @@ -1534,13 +1778,13 @@ begin end; // if left or bottom bound was not set, set them to 0 - if (Result.Left = Infinity) then + if (IsInfinite(Result.Left)) then Result.Left := 0.0; - if (Result.Bottom = Infinity) then + if (IsInfinite(Result.Bottom)) then Result.Bottom := 0.0; end; -procedure TFTFont.Render(const Text: WideString); +procedure TFTFont.Render(const Text: UCS4String); var CharIndex: integer; Glyph, PrevGlyph: TFTGlyph; @@ -1550,17 +1794,17 @@ begin PrevGlyph := nil; // draw current line - for CharIndex := 1 to Length(Text) do + for CharIndex := 0 to LengthUCS4(Text)-1 do begin Glyph := TFTGlyph(GetGlyph(Text[CharIndex])); if (Assigned(Glyph)) then begin // get kerning - if (fUseKerning and FT_HAS_KERNING(fFace) and (PrevGlyph <> nil)) then + if (fUseKerning and FT_HAS_KERNING(fFace.Data) and (PrevGlyph <> nil)) then begin - FT_Get_Kerning(fFace, PrevGlyph.CharIndex, Glyph.CharIndex, + FT_Get_Kerning(fFace.Data, PrevGlyph.CharIndex, Glyph.CharIndex, FT_KERNING_UNSCALED, KernDelta); - glTranslatef(KernDelta.x * fFontUnitScale.X, 0, 0); + glTranslatef(KernDelta.x * fFace.FontUnitScale.X, 0, 0); end; if (ReflectionPass) then @@ -1582,23 +1826,23 @@ end; function TFTFont.GetAscender(): single; begin - Result := fFace.ascender * fFontUnitScale.Y + Outset*2; + Result := fFace.Data.ascender * fFace.FontUnitScale.Y + Outset*2; end; function TFTFont.GetDescender(): single; begin // Note: outset is not part of the descender as the baseline is lifted - Result := fFace.descender * fFontUnitScale.Y; + Result := fFace.Data.descender * fFace.FontUnitScale.Y; end; function TFTFont.GetUnderlinePosition(): single; begin - Result := fFace.underline_position * fFontUnitScale.Y - Outset; + Result := fFace.Data.underline_position * fFace.FontUnitScale.Y - Outset; end; function TFTFont.GetUnderlineThickness(): single; begin - Result := fFace.underline_thickness * fFontUnitScale.Y + Outset*2; + Result := fFace.Data.underline_thickness * fFace.FontUnitScale.Y + Outset*2; end; @@ -1606,7 +1850,7 @@ end; * TFTScalableFont *} -constructor TFTScalableFont.Create(const Filename: string; +constructor TFTScalableFont.Create(const Filename: IPath; Size: integer; OutsetAmount: single; UseMipmaps: boolean); var @@ -1637,8 +1881,8 @@ begin // do not create mipmap fonts < 8 pixels if (ScaledSize < 8) then Exit; - Result := TFTFont.Create(BaseFont.fFilename, - ScaledSize, BaseFont.fOutset * Scale, + Result := TFTFont.Create(BaseFont.Filename, + ScaledSize, BaseFont.Outset * Scale, FT_LOAD_DEFAULT or FT_LOAD_NO_HINTING); end; @@ -1647,6 +1891,15 @@ begin Result := TFTFont(fBaseFont).Outset * fScale; end; +procedure TFTScalableFont.AddFallback(const Filename: IPath); +var + Level: integer; +begin + for Level := 0 to High(fMipmapFonts) do + if (fMipmapFonts[Level] <> nil) then + TFTFont(fMipmapFonts[Level]).AddFallback(Filename); +end; + procedure TFTScalableFont.FlushCache(KeepBaseSet: boolean); var Level: integer; @@ -1662,18 +1915,19 @@ end; *} constructor TFTOutlineFont.Create( - const Filename: string; + const Filename: IPath; Size: integer; Outset: single; LoadFlags: FT_Int32); begin - inherited Create(); + inherited Create(Filename); - fFilename := Filename; fSize := Size; fOutset := Outset; fInnerFont := TFTFont.Create(Filename, Size, 0.0, LoadFlags); + fInnerFont.Part := fpInner; fOutlineFont := TFTFont.Create(Filename, Size, Outset, LoadFlags); + fOutlineFont.Part := fpOutline; ResetIntern(); end; @@ -1705,7 +1959,7 @@ begin ResetIntern(); end; -procedure TFTOutlineFont.DrawUnderline(const Text: WideString); +procedure TFTOutlineFont.DrawUnderline(const Text: UCS4String); var CurrentColor: TGLColor; OutlineColor: TGLColor; @@ -1730,7 +1984,7 @@ begin glPopMatrix(); end; -procedure TFTOutlineFont.Render(const Text: WideString); +procedure TFTOutlineFont.Render(const Text: UCS4String); var CurrentColor: TGLColor; OutlineColor: TGLColor; @@ -1770,7 +2024,13 @@ begin fInnerFont.FlushCache(KeepBaseSet); end; -function TFTOutlineFont.BBox(const Text: TWideStringArray; Advance: boolean): TBoundsDbl; +procedure TFTOutlineFont.AddFallback(const Filename: IPath); +begin + fOutlineFont.AddFallback(Filename); + fInnerFont.AddFallback(Filename); +end; + +function TFTOutlineFont.BBox(const Text: TUCS4StringArray; Advance: boolean): TBoundsDbl; begin Result := fOutlineFont.BBox(Text, Advance); end; @@ -1852,7 +2112,7 @@ end; *} constructor TFTScalableOutlineFont.Create( - const Filename: string; + const Filename: IPath; Size: integer; OutsetAmount: single; UseMipmaps: boolean); var @@ -1906,6 +2166,15 @@ begin TFTOutlineFont(fMipmapFonts[Level]).FlushCache(KeepBaseSet); end; +procedure TFTScalableOutlineFont.AddFallback(const Filename: IPath); +var + Level: integer; +begin + for Level := 0 to High(fMipmapFonts) do + if (fMipmapFonts[Level] <> nil) then + TFTOutlineFont(fMipmapFonts[Level]).AddFallback(Filename); +end; + {* * TFTGlyph @@ -1935,82 +2204,119 @@ const *} cTexSmoothBorder = 1; -procedure TFTGlyph.Extrude(var TexBuffer: TGLubyteDynArray; Outset: single); +procedure TFTGlyph.StrokeBorder(var Glyph: FT_Glyph); +var + Outline: PFT_Outline; + OuterStroker, InnerStroker: FT_Stroker; + OuterNumPoints, InnerNumPoints, GlyphNumPoints: FT_UInt; + OuterNumContours, InnerNumContours, GlyphNumContours: FT_UInt; + OuterBorder, InnerBorder: FT_StrokerBorder; + OutlineFlags: FT_Int; + UseStencil: boolean; +begin + // It is possible to extrude the borders of a glyph with FT_Glyph_Stroke + // but it will extrude the border to the outside and the inside of a glyph + // although we just want to extrude to the outside. + // FT_Glyph_StrokeBorder extrudes to the outside but also fills the interior + // (this is what we need for bold fonts). + // In both cases the inner font and outline font (border) will overlap. + // Normally this does not matter but it does if alpha blending is active. + // In this case if e.g. the inner color is set to white, the outline to red + // and alpha to 0.5 the inner part will not be white it will be pink. + + InnerStroker := nil; + OuterStroker := nil; + + // If we are to create the interior of an outlined font (fInner = true) + // we have to create two borders: + // - one extruded to the outside by fOutset pixels and + // - one extruded to the inside by almost 0 zero pixels. + // The second one is used as a stencil for the first one, clearing the + // interiour of the glyph. + // The stencil is not needed to create bold fonts. + UseStencil := (fFont.Part = fpInner); + + // we cannot extrude bitmaps, only vector based glyphs. + // Check for FT_GLYPH_FORMAT_OUTLINE otherwise a cast to FT_OutlineGlyph is + // invalid and FT_Stroker_ParseOutline() will crash + if (Glyph.format <> FT_GLYPH_FORMAT_OUTLINE) then + Exit; + + Outline := @FT_OutlineGlyph(Glyph).outline; + + OuterBorder := FT_Outline_GetOutsideBorder(Outline); + if (OuterBorder = FT_STROKER_BORDER_LEFT) then + InnerBorder := FT_STROKER_BORDER_RIGHT + else + InnerBorder := FT_STROKER_BORDER_LEFT; + + { extrude outer border } + + if (FT_Stroker_New(Glyph.library_, OuterStroker) <> 0) then + raise EFontError.Create('FT_Stroker_New failed!'); + FT_Stroker_Set( + OuterStroker, + Round(fOutset * 64), + FT_STROKER_LINECAP_ROUND, + FT_STROKER_LINEJOIN_BEVEL, + 0); + + // similar to FT_Glyph_StrokeBorder(inner = FT_FALSE) but it is possible to + // use FT_Stroker_ExportBorder() afterwards to combine inner and outer borders + if (FT_Stroker_ParseOutline(OuterStroker, Outline, FT_FALSE) <> 0) then + raise EFontError.Create('FT_Stroker_ParseOutline failed!'); - procedure SetToMax(var Val1: GLubyte; Val2: GLubyte); {$IFDEF HasInline}inline;{$ENDIF} + FT_Stroker_GetBorderCounts(OuterStroker, OuterBorder, OuterNumPoints, OuterNumContours); + + { extrude inner border (= stencil) } + + if (UseStencil) then begin - if (Val1 < Val2) then - Val1 := Val2; + if (FT_Stroker_New(Glyph.library_, InnerStroker) <> 0) then + raise EFontError.Create('FT_Stroker_New failed!'); + FT_Stroker_Set( + InnerStroker, + 63, // extrude at most one pixel to avoid a black border + FT_STROKER_LINECAP_ROUND, + FT_STROKER_LINEJOIN_BEVEL, + 0); + + if (FT_Stroker_ParseOutline(InnerStroker, Outline, FT_FALSE) <> 0) then + raise EFontError.Create('FT_Stroker_ParseOutline failed!'); + + FT_Stroker_GetBorderCounts(InnerStroker, InnerBorder, InnerNumPoints, InnerNumContours); + end else begin + InnerNumPoints := 0; + InnerNumContours := 0; end; -var - I, X, Y: integer; - SrcBuffer,TmpBuffer: TGLubyteDynArray; - TexLine, TexLinePrev, TexLineNext: PGLubyteArray; - SrcLine: PGLubyteArray; - AlphaScale: single; - Value, ValueNeigh, ValueDiag: GLubyte; -const - // square-root of 2 used for diagonal neighbor pixels - cSqrt2 = 1.4142; - // number of ignored pixels on each edge of the bitmap. Consists of: - // - border used for font smoothing and - // - outer (extruded) bitmap pixel (because it is just written but never read) - cBorder = cTexSmoothBorder + 1; -begin - // allocate memory for temporary buffer - SetLength(SrcBuffer, Length(TexBuffer)); - FillChar(SrcBuffer[0], Length(TexBuffer), 0); - - // extrude pixel by pixel - for I := 1 to Ceil(Outset) do - begin - // swap arrays - TmpBuffer := TexBuffer; - TexBuffer := SrcBuffer; - SrcBuffer := TmpBuffer; - - // as long as we add an entire pixel of outset, use a solid color. - // If the fractional part is reached blend, e.g. outline=3.2 -> 3 solid - // pixels and one blended with alpha=0.2. - // For the fractional part I = Ceil(Outset) is always true. - if (I <= Outset) then - AlphaScale := 1 - else - AlphaScale := Outset - Trunc(Outset); - - // copy data to the expanded bitmap. - for Y := cBorder to fTexSize.Height - 2*cBorder do - begin - TexLine := @TexBuffer[Y*fTexSize.Width]; - TexLinePrev := @TexBuffer[(Y-1)*fTexSize.Width]; - TexLineNext := @TexBuffer[(Y+1)*fTexSize.Width]; - SrcLine := @SrcBuffer[Y*fTexSize.Width]; + { combine borders (subtract: OuterBorder - InnerBorder) } - // expand current line's pixels - for X := cBorder to fTexSize.Width - 2*cBorder do - begin - Value := SrcLine[X]; - ValueNeigh := Round(Value * AlphaScale); - ValueDiag := Round(ValueNeigh / cSqrt2); + GlyphNumPoints := InnerNumPoints + OuterNumPoints; + GlyphNumContours := InnerNumContours + OuterNumContours; - SetToMax(TexLine[X], Value); - SetToMax(TexLine[X-1], ValueNeigh); - SetToMax(TexLine[X+1], ValueNeigh); + // save flags before deletion (TODO: set them on the resulting outline) + OutlineFlags := Outline.flags; - SetToMax(TexLinePrev[X], ValueNeigh); - SetToMax(TexLinePrev[X-1], ValueDiag); - SetToMax(TexLinePrev[X+1], ValueDiag); + // resize glyph outline to hold inner and outer border + FT_Outline_Done(Glyph.Library_, Outline); + if (FT_Outline_New(Glyph.Library_, GlyphNumPoints, GlyphNumContours, Outline) <> 0) then + raise EFontError.Create('FT_Outline_New failed!'); - SetToMax(TexLineNext[X], ValueNeigh); - SetToMax(TexLineNext[X-1], ValueDiag); - SetToMax(TexLineNext[X+1], ValueDiag); - end; - end; - end; + Outline.n_points := 0; + Outline.n_contours := 0; - TmpBuffer := nil; - SetLength(SrcBuffer, 0); + // add points to outline. The inner-border is used as a stencil. + FT_Stroker_ExportBorder(OuterStroker, OuterBorder, Outline); + if (UseStencil) then + FT_Stroker_ExportBorder(InnerStroker, InnerBorder, Outline); + if (FT_Outline_Check(outline) <> 0) then + raise EFontError.Create('FT_Stroker_ExportBorder failed!'); + + if (InnerStroker <> nil) then + FT_Stroker_Done(InnerStroker); + if (OuterStroker <> nil) then + FT_Stroker_Done(OuterStroker); end; procedure TFTGlyph.CreateTexture(LoadFlags: FT_Int32); @@ -2025,17 +2331,26 @@ var TexLine: PGLubyteArray; CBox: FT_BBox; begin + // we need vector data for outlined glyphs so do not load bitmaps. + // This is necessary for mixed fonts that contain bitmap versions of smaller + // glyphs, for example in CJK fonts. + if (fOutset > 0) then + LoadFlags := LoadFlags or FT_LOAD_NO_BITMAP; + // load the Glyph for our character - if (FT_Load_Glyph(fFont.Face, fCharIndex, LoadFlags) <> 0) then - raise Exception.Create('FT_Load_Glyph failed'); + if (FT_Load_Glyph(fFace.Data, fCharIndex, LoadFlags) <> 0) then + raise EFontError.Create('FT_Load_Glyph failed'); // move the face's glyph into a Glyph object - if (FT_Get_Glyph(fFont.Face^.glyph, Glyph) <> 0) then - raise Exception.Create('FT_Get_Glyph failed'); + if (FT_Get_Glyph(fFace.Data^.glyph, Glyph) <> 0) then + raise EFontError.Create('FT_Get_Glyph failed'); + + if (fOutset > 0) then + StrokeBorder(Glyph); // store scaled advance width/height in glyph-object - fAdvance.X := fFont.Face^.glyph^.advance.x / 64 + fOutset*2; - fAdvance.Y := fFont.Face^.glyph^.advance.y / 64 + fOutset*2; + fAdvance.X := fFace.Data^.glyph^.advance.x / 64 + fOutset*2; + fAdvance.Y := fFace.Data^.glyph^.advance.y / 64 + fOutset*2; // get the contour's bounding box (in 1/64th pixels, not font-units) FT_Glyph_Get_CBox(Glyph, FT_GLYPH_BBOX_UNSCALED, CBox); @@ -2114,9 +2429,6 @@ begin end; end; - if (fOutset > 0) then - Extrude(TexBuffer, fOutset); - // allocate resources for textures and display lists glGenTextures(1, @fTexture); @@ -2151,16 +2463,36 @@ begin FT_Done_Glyph(Glyph); end; -constructor TFTGlyph.Create(Font: TFTFont; ch: WideChar; Outset: single; +constructor TFTGlyph.Create(Font: TFTFont; ch: UCS4Char; Outset: single; LoadFlags: FT_Int32); +var + I: integer; begin inherited Create(); fFont := Font; fOutset := Outset; + fCharCode := ch; - // get the Freetype char-index (use default UNICODE charmap) - fCharIndex := FT_Get_Char_Index(Font.fFace, FT_ULONG(ch)); + // Note: the default face is also used if no face (neither default nor fallback) + // contains a glyph for the given char. + fFace := Font.DefaultFace; + + // search the Freetype char-index (use default UNICODE charmap) in the default face + fCharIndex := FT_Get_Char_Index(fFace.Data, FT_ULONG(ch)); + if (fCharIndex = 0) then + begin + // glyph not in default font, search in fallback font faces + for I := 0 to High(Font.FallbackFaces) do + begin + fCharIndex := FT_Get_Char_Index(Font.FallbackFaces[I].Data, FT_ULONG(ch)); + if (fCharIndex <> 0) then + begin + fFace := Font.FallbackFaces[I]; + Break; + end; + end; + end; CreateTexture(LoadFlags); end; @@ -2336,7 +2668,7 @@ begin InsertPos := fHash.Count; end; -function TGlyphCache.AddGlyph(ch: WideChar; const Glyph: TGlyph): boolean; +function TGlyphCache.AddGlyph(ch: UCS4Char; const Glyph: TGlyph): boolean; var BaseCode: cardinal; GlyphCode: integer; @@ -2346,7 +2678,7 @@ var begin Result := false; - BaseCode := cardinal(ch) shr 8; + BaseCode := Ord(ch) shr 8; GlyphTable := FindGlyphTable(BaseCode, InsertPos); if (GlyphTable = nil) then begin @@ -2356,7 +2688,7 @@ begin end; // get glyph table offset - GlyphCode := cardinal(ch) and $FF; + GlyphCode := Ord(ch) and $FF; // insert glyph into table if not present if (GlyphTable[GlyphCode] = nil) then begin @@ -2365,19 +2697,19 @@ begin end; end; -procedure TGlyphCache.DeleteGlyph(ch: WideChar); +procedure TGlyphCache.DeleteGlyph(ch: UCS4Char); var Table: PGlyphTable; TableIndex, GlyphIndex: integer; TableEmpty: boolean; begin // find table - Table := FindGlyphTable(cardinal(ch) shr 8, TableIndex); + Table := FindGlyphTable(Ord(ch) shr 8, TableIndex); if (Table = nil) then Exit; // find glyph - GlyphIndex := cardinal(ch) and $FF; + GlyphIndex := Ord(ch) and $FF; if (Table[GlyphIndex] <> nil) then begin // destroy glyph @@ -2402,19 +2734,19 @@ begin end; end; -function TGlyphCache.GetGlyph(ch: WideChar): TGlyph; +function TGlyphCache.GetGlyph(ch: UCS4Char): TGlyph; var InsertPos: integer; Table: PGlyphTable; begin - Table := FindGlyphTable(cardinal(ch) shr 8, InsertPos); + Table := FindGlyphTable(Ord(ch) shr 8, InsertPos); if (Table = nil) then Result := nil else - Result := Table[cardinal(ch) and $FF]; + Result := Table[Ord(ch) and $FF]; end; -function TGlyphCache.HasGlyph(ch: WideChar): boolean; +function TGlyphCache.HasGlyph(ch: UCS4Char): boolean; begin Result := (GetGlyph(ch) <> nil); end; @@ -2464,7 +2796,7 @@ begin begin // initialize freetype if (FT_Init_FreeType(LibraryInst) <> 0) then - raise Exception.Create('FT_Init_FreeType failed'); + raise EFontError.Create('FT_Init_FreeType failed'); end; Result := LibraryInst; end; @@ -2482,10 +2814,10 @@ end; * TBitmapFont *} -constructor TBitmapFont.Create(const Filename: string; Outline: integer; +constructor TBitmapFont.Create(const Filename: IPath; Outline: integer; Baseline, Ascender, Descender: integer); begin - inherited Create(); + inherited Create(Filename); fTex := Texture.LoadTexture(true, Filename, TEXTURE_TYPE_TRANSPARENT, 0); fTexSize := 1024; @@ -2494,7 +2826,7 @@ begin fAscender := Ascender; fDescender := Descender; - LoadFontInfo(ChangeFileExt(Filename, '.dat')); + LoadFontInfo(Filename.SetExtension('.dat')); ResetIntern(); end; @@ -2516,6 +2848,11 @@ begin ResetIntern(); end; +procedure TBitmapFont.AddFallback(const Filename: IPath); +begin + // no support for fallbacks +end; + procedure TBitmapFont.CorrectWidths(WidthMult: real; WidthAdd: integer); var Count: integer; @@ -2524,27 +2861,27 @@ begin fWidths[Count] := Round(fWidths[Count] * WidthMult) + WidthAdd; end; -procedure TBitmapFont.LoadFontInfo(const InfoFile: string); +procedure TBitmapFont.LoadFontInfo(const InfoFile: IPath); var - Stream: TFileStream; + Stream: TStream; begin FillChar(fWidths[0], Length(fWidths), 0); Stream := nil; try - Stream := TFileStream.Create(InfoFile, fmOpenRead); + Stream := TBinaryFileStream.Create(InfoFile, fmOpenRead); Stream.Read(fWidths, 256); except - raise Exception.Create('Could not read font info file ''' + InfoFile + ''''); + raise EFontError.Create('Could not read font info file ''' + InfoFile.ToNative + ''''); end; Stream.Free; end; -function TBitmapFont.BBox(const Text: TWideStringArray; Advance: boolean): TBoundsDbl; +function TBitmapFont.BBox(const Text: TUCS4StringArray; Advance: boolean): TBoundsDbl; var LineIndex, CharIndex: integer; CharCode: cardinal; - Line: WideString; + Line: UCS4String; LineWidth: double; begin Result.Left := 0; @@ -2556,7 +2893,7 @@ begin begin Line := Text[LineIndex]; LineWidth := 0; - for CharIndex := 1 to Length(Line) do + for CharIndex := 0 to LengthUCS4(Line)-1 do begin CharCode := Ord(Line[CharIndex]); if (CharCode < Length(fWidths)) then @@ -2567,7 +2904,7 @@ begin end; end; -procedure TBitmapFont.RenderChar(ch: WideChar; var AdvanceX: real); +procedure TBitmapFont.RenderChar(ch: UCS4Char; var AdvanceX: real); var TexX, TexY: real; TexR, TexB: real; @@ -2659,20 +2996,20 @@ begin AdvanceX := AdvanceX + GlyphWidth; end; -procedure TBitmapFont.Render(const Text: WideString); +procedure TBitmapFont.Render(const Text: UCS4String); var CharIndex: integer; AdvanceX: real; begin // if there is no text do nothing - if (Text = '') then + if (Text = nil) or (Text[0] = 0) then Exit; //Save the current color and alpha (for reflection) glGetFloatv(GL_CURRENT_COLOR, @fTempColor); AdvanceX := 0; - for CharIndex := 1 to Length(Text) do + for CharIndex := 0 to LengthUCS4(Text)-1 do begin RenderChar(Text[CharIndex], AdvanceX); end; -- cgit v1.2.3