unit utsFontCreatorFreeType; {$mode objfpc}{$H+} interface uses Classes, SysUtils, syncobjs, dynlibs, utsTextSuite, utsTypes, utsFreeType; type //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// TtsFreeTypeFaceHandle = class private fFace: FT_Face; public constructor Create(const aFace: FT_Face); destructor Destroy; override; end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// TtsFontFreeType = class(TtsFont) private fHandle: TtsFreeTypeFaceHandle; public constructor Create(const aHandle: TtsFreeTypeFaceHandle; const aRenderer: TtsRenderer; const aGenerator: TtsFontGenerator; const aProperties: TtsFontProperties); destructor Destroy; override; end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// TtsFontGeneratorFreeType = class(TtsFontGenerator) private fHandle: FT_Library; function ConvertFont(const aFont: TtsFont): TtsFontFreeType; procedure LoadNames(const aFace: FT_Face; var aProperties: TtsFontProperties); protected function GetGlyphMetrics(const aFont: TtsFont; const aCharCode: WideChar; out aGlyphOrigin, aGlyphSize: TtsPosition; out aAdvance: Integer): Boolean; override; procedure GetCharImage(const aFont: TtsFont; const aCharCode: WideChar; const aCharImage: TtsImage); override; public function GetFontByFile(const aFilename: String; const aRenderer: TtsRenderer; const aSize: Integer; const aAntiAliasing: TtsAntiAliasing): TtsFont; overload; constructor Create(const aContext: TtsContext); destructor Destroy; override; end; implementation uses utsUtils, math; const FT_SIZE_FACTOR = 64; FT_SIZE_RES = 72; //dpi //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //TtsFreeTypeFaceHandle///////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// constructor TtsFreeTypeFaceHandle.Create(const aFace: FT_Face); begin inherited Create; fFace := aFace; end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// destructor TtsFreeTypeFaceHandle.Destroy; begin FT_Done_Face(fFace); inherited Destroy; end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //TtsFontFreeType/////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// constructor TtsFontFreeType.Create(const aHandle: TtsFreeTypeFaceHandle; const aRenderer: TtsRenderer; const aGenerator: TtsFontGenerator; const aProperties: TtsFontProperties); begin inherited Create(aRenderer, aGenerator, aProperties); fHandle := aHandle; end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// destructor TtsFontFreeType.Destroy; begin FreeAndNil(fHandle); inherited Destroy; end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //TtsFontGeneratorFreeType////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// function TtsFontGeneratorFreeType.ConvertFont(const aFont: TtsFont): TtsFontFreeType; begin if not (aFont is TtsFontFreeType) then raise EtsException.Create('aFont need to be a TtsFontGDI object'); result := (aFont as TtsFontFreeType); end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// procedure TtsFontGeneratorFreeType.LoadNames(const aFace: FT_Face; var aProperties: TtsFontProperties); var i, cnt: FT_Int; err: FT_Error; name: FT_SfntName; function DecodeAnsi(const aCodePage: TtsCodePage): String; var tmp: WideString; len: Integer; begin SetLength(tmp, name.string_len); len := tsAnsiSBCDToWide(@tmp[1], name.string_len, PAnsiChar(name.string_), aCodePage, '?'); SetLength(tmp, len); result := UTF8Encode(tmp); end; function Decode: String; var tmp: WideString; len: Integer; begin result := ''; case name.platform_id of TT_PLATFORM_APPLE_UNICODE: begin case name.encoding_id of TT_APPLE_ID_DEFAULT, TT_APPLE_ID_UNICODE_1_1, TT_APPLE_ID_UNICODE_2_0: begin SetLength(tmp, name.string_len); len := tsUTFBE16ToWide(@tmp[1], name.string_len, name.string_, name.string_len, '?'); SetLength(tmp, len); result := UTF8Encode(tmp); end; end; end; TT_PLATFORM_ISO: begin case name.encoding_id of TT_ISO_ID_8859_1: result := DecodeAnsi(tsISO_8859_1); end; end; end; end; begin cnt := FT_Get_Sfnt_Name_Count(aFace); for i := 0 to cnt-1 do begin err := FT_Get_Sfnt_Name(aFace, i, @name); if (err <> 0) then continue; case name.name_id of TT_NAME_ID_COPYRIGHT: if (aProperties.Copyright = '') then aProperties.Copyright := Decode; TT_NAME_ID_FONT_FAMILY: if (aProperties.Fontname = '') then aProperties.Fontname := Decode; TT_NAME_ID_FULL_NAME: if (aProperties.FullName = '') then aProperties.FullName := Decode; end; end; end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// function TtsFontGeneratorFreeType.GetGlyphMetrics(const aFont: TtsFont; const aCharCode: WideChar; out aGlyphOrigin, aGlyphSize: TtsPosition; out aAdvance: Integer): Boolean; var font: TtsFontFreeType; err: FT_Error; begin result := false; aGlyphOrigin.x := 0; aGlyphOrigin.x := 0; aGlyphSize.x := 0; aGlyphSize.y := 0; aAdvance := 0; font := ConvertFont(aFont); case font.Properties.AntiAliasing of tsAANormal: err := FT_Load_Char(font.fHandle.fFace, Ord(aCharCode), FT_LOAD_DEFAULT); tsAANone: err := FT_Load_Char(font.fHandle.fFace, Ord(aCharCode), FT_LOAD_MONOCHROME); else raise EtsException.Create('unknown anti aliasing'); end; case err of FT_ERR_None: { nop }; FT_ERR_Invalid_Character_Code: exit; else raise EtsException.Create('unable to set glyph metrix: error=' + IntToStr(err)); end; result := true; with font.fHandle.fFace^.glyph^.metrics do begin aAdvance := horiAdvance div FT_SIZE_FACTOR; aGlyphOrigin.x := horiBearingX div FT_SIZE_FACTOR; aGlyphOrigin.y := horiBearingY div FT_SIZE_FACTOR; aGlyphSize.x := width div FT_SIZE_FACTOR; aGlyphSize.y := height div FT_SIZE_FACTOR; end; end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// procedure TtsFontGeneratorFreeType.GetCharImage(const aFont: TtsFont; const aCharCode: WideChar; const aCharImage: TtsImage); var font: TtsFontFreeType; err: FT_Error; g: FT_GlyphSlot; b: PFT_Bitmap; procedure CopyGray; var x, y: Integer; src, dst: PByte; c: TtsColor4f; begin aCharImage.CreateEmpty(font.Renderer.Format, b^.width, b^.rows); c := tsColor4f(1, 1, 1, 1); for y := 0 to b^.rows-1 do begin src := b^.buffer + y * b^.pitch; dst := aCharImage.Scanline[y]; for x := 0 to b^.width-1 do begin c.a := src^ / $FF; inc(src, 1); tsFormatMap(aCharImage.Format, dst, c); end; end; end; procedure CopyMono; var x, y, i, cnt: Integer; src, dst: PByte; tmp: Byte; c: TtsColor4f; begin aCharImage.CreateEmpty(font.Renderer.Format, b^.width, b^.rows); c := tsColor4f(1, 1, 1, 1); for y := 0 to b^.rows-1 do begin src := b^.buffer + y * b^.pitch; dst := aCharImage.Scanline[y]; x := b^.width; while (x > 0) do begin cnt := min(8, x); tmp := src^; inc(src, 1); for i := 1 to cnt do begin if ((tmp and $80) > 0) then c.a := 1.0 else c.a := 0.0; tmp := (tmp and not $80) shl 1; tsFormatMap(aCharImage.Format, dst, c); end; dec(x, cnt); end; end; end; begin font := ConvertFont(aFont); g := font.fHandle.fFace^.glyph; if not (font.Properties.AntiAliasing in [tsAANormal, tsAANone]) then raise Exception.Create('unknown anti aliasing'); case font.Properties.AntiAliasing of tsAANormal: err := FT_Load_Char(font.fHandle.fFace, Ord(aCharCode), FT_LOAD_DEFAULT or FT_LOAD_RENDER); tsAANone: err := FT_Load_Char(font.fHandle.fFace, Ord(aCharCode), FT_LOAD_MONOCHROME or FT_LOAD_TARGET_MONO or FT_LOAD_RENDER); end; if (err <> 0) then raise EtsException.Create('unable to set glyph metrix: error=' + IntToStr(err)); if (g^.format <> FT_GLYPH_FORMAT_BITMAP) then raise EtsException.Create('invalid glyph format'); b := @g^.bitmap; case b^.pixel_mode of FT_PIXEL_MODE_MONO: CopyMono; FT_PIXEL_MODE_GRAY: CopyGray; else raise EtsException.Create('unknown glyph bitmap format'); end; end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// function TtsFontGeneratorFreeType.GetFontByFile(const aFilename: String; const aRenderer: TtsRenderer; const aSize: Integer; const aAntiAliasing: TtsAntiAliasing): TtsFont; var face: FT_Face; err: FT_Error; prop: TtsFontProperties; begin err := FT_New_Face(fHandle, PAnsiChar(aFilename), 0, @face); if (err <> 0) then raise EtsException.Create('unable to create free type face from file: ' + aFilename + ' error=' + IntToStr(err)); err := FT_Set_Char_Size(face, 0, aSize * FT_SIZE_FACTOR, FT_SIZE_RES, FT_SIZE_RES); if (err <> 0) then raise EtsException.Create('unable to set char size: error=' + IntToStr(err)); FillByte(prop, SizeOf(prop), 0); prop.AntiAliasing := tsAANormal; prop.FaceName := face^.family_name; prop.StyleName := face^.style_name; LoadNames(face, prop); prop.Size := aSize; prop.AntiAliasing := aAntiAliasing; prop.DefaultChar := '?'; prop.Style := []; if ((face^.style_flags and FT_STYLE_FLAG_BOLD) <> 0) then Include(prop.Style, tsStyleBold); if ((face^.style_flags and FT_STYLE_FLAG_ITALIC) <> 0) then Include(prop.Style, tsStyleItalic); prop.Ascent := face^.size^.metrics.ascender div FT_SIZE_FACTOR; prop.Descent := -face^.size^.metrics.descender div FT_SIZE_FACTOR; prop.ExternalLeading := 0; prop.BaseLineOffset := 0; prop.UnderlinePos := face^.underline_position div FT_SIZE_FACTOR; prop.UnderlineSize := face^.underline_thickness div FT_SIZE_FACTOR; prop.StrikeoutPos := 0; prop.StrikeoutSize := 0; result := TtsFontFreeType.Create(TtsFreeTypeFaceHandle.Create(face), aRenderer, self, prop); end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// constructor TtsFontGeneratorFreeType.Create(const aContext: TtsContext); begin inherited Create(aContext); fHandle := InitFreeType; end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// destructor TtsFontGeneratorFreeType.Destroy; begin inherited Destroy; // first call interited QuitFreeType; // QuitFreeType will free callpacks end; end.