Delphi XE5开发Android程序使用自定义字体文件.

万事大吉,只欠根据字体文件(.ttf文件)切换阅读字体,通常Android系统只带三种以下字体.一般用Java/Eclipse开发的话比较简单,typeface的createFromAsset,createFromFile之类的很容易使用.
但是由于FireMonkey是跨平台的类库,必然不能和平台帮得太紧,所以提供了抽象的封装.
但是也许Delphi XE5是Android平台的第一个版本,有些地方难免有疏漏,FireMonkey的封装没有提供更换字体的功能.

但是我要实现的电子书阅读器换字体几乎是必须要实现的功能,所以只能给FireMonkey动动小手术了.

FireMonkey的字体加载是由抽象类TFontGlyphManager来实现的,在各个具体平台又有不同的实现,TWinFontGlyphManager,TIOSFontGlyphManager,TMacFontGlyphManager,TAndroidFontGlyphManager.

我们这里只针对Android不能加载字体文件换字体进行手术.

把TAndroidFontGlyphManager的实现单元FMX.FontGlyphs.Android拷贝到我们自己要使用更换字的的工程的目录中.修改TAndroidFontGlyphManager.LoadResource方法,当应用某字体的时候先判断我们指定的目录中是否存在同名的.ttf文件.有的话优先使用我们的字体文件.

做了两处改动.一处是uses添加了System.IOUtils单元.一处是TAndroidFontGlyphManager.LoadResource.
在这里做这样的小手术好处是我们的程序不收任何影响.例如:
Text1.Font.Family:=’微软雅黑’;
Text2.Font.Family:=’楷体’;
那么只要在我们指定的目录中存在”楷体.ttf”和”微软雅黑.ttf”那么这两个控件的字体就会分别应用对应的字体文件.

希望XE6版本中Android能投提供一种让我们动态加载字体的办法.不过也许我这个不是一个大众需求,毕竟大多数Android软件不需要太多的字体文件,在系统两三款字体下也活得好好的.

下面贴出来我修改过的文件.

{ ******************************************************* }
{ }
{ Delphi FireMonkey Platform }
{ Copyright(c) 2013 Embarcadero Technologies, Inc. }
{ }
{ ******************************************************* }
 
unit FMX . FontGlyphs . Android;
 
interface
 
uses
   System . Types, System . Classes, System . SysUtils, System . UITypes,
   System . UIConsts, System . Generics . Collections,
   FMX . Types, FMX . Surfaces, FMX . FontGlyphs, FMX . PixelFormats,
   Androidapi . JNI . JavaTypes, Androidapi . JNI . GraphicsContentViewText,
   Androidapi . JNIBridge;
 
{$SCOPEDENUMS ON}
 
type
 
   TAndroidFontGlyphManager = class (TFontGlyphManager)
   private
     FPaint: JPaint;
     // Current metrics
     FSpacing: Single ;
     FTop: Single ;
     FTopInt: Integer ;
     FAscent: Single ;
     FDescent: Single ;
     FBottom: Single ;
     FBottomInt: Integer ;
     FLeading: Single ;
     FLeadingInt: Integer ;
   protected
     procedure LoadResource; override;
     procedure FreeResource; override;
     function DoGetGlyph( const Char : UCS4Char;
       const Settings: TFontGlyphSettings): TFontGlyph; override;
   public
     constructor Create;
     destructor Destroy; override;
   end ;
 
implementation
 
uses
   System . Math, System . Character,
   Androidapi . Bitmap,
   //引入System.IOUtils是为了能够获取Android的各种系统目录
   System . IOUtils,
   //
   FMX . Graphics;
 
{ TAndroidFontGlyphManager }
 
constructor TAndroidFontGlyphManager . Create;
begin
   inherited Create;
   FPaint := TJPaint . Create;
end ;
 
destructor TAndroidFontGlyphManager . Destroy;
begin
   FPaint := nil ;
   inherited ;
end ;
 
procedure TAndroidFontGlyphManager . LoadResource;
const
   BoldAndItalic = [TFontStyle . fsBold, TFontStyle . fsItalic];
var
   TypefaceFlag: Integer ;
   Typeface: JTypeface;
   FamilyName: JString;
   Metrics: JPaint_FontMetrics;
   MetricsInt: JPaint_FontMetricsInt;
   FontFile: string ;
begin
   FPaint . setAntiAlias( True );
   FPaint . setTextSize(CurrentSettings . Size * CurrentSettings . Scale);
   FPaint . setARGB( 255 , 255 , 255 , 255 );
   FPaint . setUnderlineText(TFontStyle . fsUnderline in CurrentSettings . Style);
   FPaint . setStrikeThruText(TFontStyle . fsStrikeOut in CurrentSettings . Style);
   if TOSVersion . Check( 4 , 0 ) then
     FPaint . setHinting(TJPaint . JavaClass . HINTING_ON);
   // Font
   try
     FamilyName := StringToJString(CurrentSettings . Family);
     if (BoldAndItalic * CurrentSettings . Style) = BoldAndItalic then
       TypefaceFlag := TJTypeface . JavaClass . BOLD_ITALIC
     else if TFontStyle . fsBold in CurrentSettings . Style then
       TypefaceFlag := TJTypeface . JavaClass . BOLD
     else if TFontStyle . fsItalic in CurrentSettings . Style then
       TypefaceFlag := TJTypeface . JavaClass . ITALIC
     else
       TypefaceFlag := TJTypeface . JavaClass . NORMAL;
 
     { Fix Begin 修改开始.如果在下载目录中存在跟字体同名的.ttf文件,那么优先使用ttf文件.
       我是放在SD卡的下载目录中.大家可以按需要任意改这个位置.
       甚至也可以放在Asset目录中,这样可以打包在APK中.
     }
     FontFile := TPath . GetSharedDownloadsPath + PathDelim +
       CurrentSettings . Family + '.ttf' ;
     if FileExists(FontFile) then
       Typeface := TJTypeface . JavaClass . createFromFile(StringToJString(FontFile))
     else
       Typeface := TJTypeface . JavaClass . Create(FamilyName, TypefaceFlag);
     { Fix End 修改结束 }
     FPaint . setTypeface(Typeface);
     try
       Metrics := FPaint . getFontMetrics;
       MetricsInt := FPaint . getFontMetricsInt;
       //
       FSpacing := FPaint . getFontMetrics(Metrics);
       FTop := Metrics . top;
       FTopInt := MetricsInt . top;
       FAscent := Metrics . ascent;
       FDescent := Metrics . descent;
       FBottom := Metrics . bottom;
       FBottomInt := MetricsInt . bottom;
       FLeading := Metrics . leading;
       FLeadingInt := MetricsInt . leading;
 
       // SysDebug(FloatToStr(CurrentSettings.Size) + ':' + FloatToStr(CurrentSettings.Scale));
       // Log.d(Format('Top=(%d %f) Bottom=(%d %f) Leading=(%d %f) FAscent=(%d %f)', [FTopInt, FTop, FBottomInt, FBottom, FLeadingInt, FLeading, MetricsInt.ascent, FAscent]));
     finally
       Metrics := nil ;
       MetricsInt := nil ;
     end ;
   finally
     FamilyName := nil ;
     Typeface := nil ;
   end ;
end ;
 
procedure TAndroidFontGlyphManager . FreeResource;
begin
   if Assigned(FPaint) then
     FPaint . reset;
end ;
 
function TAndroidFontGlyphManager . DoGetGlyph( const Char : UCS4Char;
   const Settings: TFontGlyphSettings): TFontGlyph;
var
   Text: JString;
   Bitmap: JBitmap;
   Canvas: JCanvas;
   GlyphRect: TRect;
   C, I, J, Width, Height: Integer ;
   Advance: Single ;
   Bounds: JRect;
   GlyphStyle: TFontGlyphStyles;
   PixelBuffer: Pointer ;
   Data: PIntegerArray;
   Path: JPath;
   PathMeasure: JPathMeasure;
   PathLength: Single ;
   Coords: TJavaArray< Single >;
   StartPoint, LastPoint, Point: TPointF;
   NewContour, HasStartPoint: Boolean ;
begin
   try
     Text := StringToJString(System . Char . ConvertFromUtf32( Char ));
     Advance := FPaint . measureText(Text);
     // SysDebug(Format('%s %f', [System.Char.ConvertFromUtf32(Char), Advance]));
     Height := Abs (FTopInt) + Abs (FBottomInt) + 2 ;
     Width := Ceil( Abs (Advance)) + 2 ;
     try
       Bitmap := TJBitmap . JavaClass . createBitmap(Width, Height,
         TJBitmap_Config . JavaClass . ARGB_8888);
       try
         Bounds := TJRect . Create;
         FPaint . getTextBounds(Text, 0 , Text . length, Bounds);
         // Log.d(Format('Bounds=(%d %d %d %d) %d %d ', [Bounds.left, Bounds.top, Bounds.right, Bounds.bottom, Bounds.width, Bounds.height]));
         try
           Canvas := TJCanvas . JavaClass . init(Bitmap);
           Canvas . drawText(Text, 0 , -Trunc(FAscent), FPaint);
         finally
           Canvas := nil ;
         end ;
 
         GlyphStyle := [];
         if ((FAscent = 0 ) and (FDescent = 0 )) or not HasGlyph( Char ) then
           GlyphStyle := [TFontGlyphStyle . NoGlyph];
         if TFontGlyphSetting . gsPath in Settings then
           GlyphStyle := GlyphStyle + [TFontGlyphStyle . HasPath];
 
         Result := TFontGlyph . Create(TPoint . Create(Bounds . left,
           Abs (FTopInt - Bounds . top)), Advance, Abs (FTopInt) + Abs (FBottomInt) +
           Abs (FLeadingInt), GlyphStyle);
 
         if (TFontGlyphSetting . gsBitmap in Settings) and
           (HasGlyph( Char ) or ((FAscent <> 0 ) or (FDescent <> 0 ))) and
           (AndroidBitmap_lockPixels(TJNIResolver . GetJNIEnv,
           (Bitmap as ILocalObject).GetObjectID, @PixelBuffer) = 0 ) then
         begin
           Data := PIntegerArray(PixelBuffer);
           GlyphRect . left := Bounds . left;
           GlyphRect . Right := Bounds . Right;
           GlyphRect . top := Abs (Trunc(FAscent) - Bounds . top);
           GlyphRect . bottom := Abs (Trunc(FAscent) - Bounds . bottom);
           // Log.d(Format('GlyphRect=(%d %d %d %d) %d %d', [GlyphRect.Left, GlyphRect.Top, GlyphRect.Right, GlyphRect.Bottom, GlyphRect.Width, GlyphRect.Height]));
 
           if (GlyphRect . Width > 0 ) or (GlyphRect . Height > 0 ) then
           begin
             Result . Bitmap . SetSize(GlyphRect . Width + 1 , GlyphRect . Height + 1 ,
               TPixelFormat . pfA8R8G8B8);
             if TFontGlyphSetting . gsPremultipliedAlpha in Settings then
             begin
               for I := GlyphRect . top to GlyphRect . bottom do
                 Move(Data[I * Width + Max(GlyphRect . left, 0 )],
                   Result . Bitmap . GetPixelAddr( 0 , I - GlyphRect . top)^,
                   Result . Bitmap . Pitch);
             end
             else
               for I := GlyphRect . top to GlyphRect . bottom - 1 do
                 for J := GlyphRect . left to GlyphRect . Right - 1 do
                 begin
                   C := Data[I * Width + J];
                   if C <> 0 then
                   begin
                     C := ((C shr 16 ) and $FF + (C shr 8 ) and
                       $FF + (C and $FF )) div 3 ;
                     Result . Bitmap . Pixels[J - GlyphRect . left, I - GlyphRect . top]
                       := MakeColor( $FF , $FF , $FF , C);
                   end
                 end ;
           end ;
           AndroidBitmap_unlockPixels(TJNIResolver . GetJNIEnv,
             (Bitmap as ILocalObject).GetObjectID);
         end ;
         // Path
         if TFontGlyphSetting . gsPath in Settings then
           try
             Path := TJPath . Create;
             FPaint . getTextPath(Text, 0 , Text . length, Result . Origin . X,
               Result . Origin . Y, Path);
             PathMeasure := TJPathMeasure . Create;
             PathMeasure . setPath(Path, False );
             Coords := TJavaArray< Single >.Create( 2 );
             if PathMeasure . getLength > 0 then
               repeat
                 PathLength := PathMeasure . getLength;
                 NewContour := True ;
                 HasStartPoint := False ;
                 I := 0 ;
                 while I < PathLength do
                 begin
                   if PathMeasure . getPosTan(I, Coords, nil ) then
                   begin
                     Point := PointF(Coords[ 0 ], Coords[ 1 ]);
                     if NewContour then
                     begin
                       Result . Path . MoveTo(Point);
                       NewContour := False ;
                       HasStartPoint := False ;
                     end
                     else if Point <> LastPoint then
                     begin
                       if HasStartPoint and (LastPoint <> StartPoint) then
                         if not SameValue
                           (((Point . Y - StartPoint . Y) / (Point . X - StartPoint . X)
                           ), ((Point . Y - LastPoint . Y) / (Point . X - LastPoint . X)
                           ), Epsilon) then
                         begin
                           Result . Path . LineTo(Point);
                           HasStartPoint := False ;
                         end
                         else
                       else
                         Result . Path . LineTo(Point);
                     end ;
                     LastPoint := Point;
                     if not HasStartPoint then
                     begin
                       StartPoint := Point;
                       HasStartPoint := True ;
                     end ;
                   end ;
                   Inc(I);
                 end ;
                 if Result . Path . Count > 0 then
                   Result . Path . ClosePath;
               until not PathMeasure . nextContour;
             Point := Result . Path . GetBounds . TopLeft;
             Result . Path . Translate(-Point . X + Result . Origin . X,
               -Point . Y + Result . Origin . Y);
           finally
             FreeAndNil(Coords);
             Path := nil ;
             PathMeasure := nil ;
           end ;
       finally
         Bounds := nil ;
       end ;
     finally
       Bitmap . recycle;
       Bitmap := nil ;
     end ;
   finally
     Text := nil ;
   end ;
end ;
 
end .

Delphi XE5开发Android程序使用自定义字体文件.

你可能感兴趣的:(android)