zoukankan      html  css  js  c++  java
  • 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.
  • 相关阅读:
    Gradle在大型Java项目上的应用
    2015年,移动开发都有哪些热点?
    为什么寄存器比内存快?
    Gogs:可能是比Gitlab更好的选择
    自定义元素–为你的HTML代码定义新元素
    在DLL编程中,导出函数为什么需要extern "C"
    c调用c++编的dll,c++调用c编写的dll,extern “C”的用法
    C/C++:函数的编译方式与调用约定以及extern “C”的使用
    在VS2015中用C++编写可被其它语言调用的动态库DLL
    C++在VS下创建、调用dll
  • 原文地址:https://www.cnblogs.com/china1/p/3415471.html
Copyright © 2011-2022 走看看