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.

 

你可能感兴趣的:(android)