万事大吉,只欠根据字体文件(.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程序使用自定义字体文件.的更多相关文章

  1. XE5开发Android程序调用电话相关功能(短信息和电话)

    方法a.不使用Intent而是直接发短信. smsManager对应的Delphi代码应该是: uses Androidapi.JNI.JavaTypes,Androidapi.JNI.Telepho ...

  2. XE5开发Android程序调用电话相关功能(短信息和电话) [转]

    其实都可以通过intent和URI调用系统功能.Windows程序员可以理解成是ShellExecute.这个是万金油.可以有调用各种功能.后面会介绍. 1.短信息.很简单 方法a.不使用Intent ...

  3. 解决 Delphi XE5 写Android程序的No resource identifier found for attribute... 错误【转】

    原文:http://www.hxhlb.cn/article/32142aaeb67bbc05379369c3.html 那一天,我装上了RAD Studio XE5. 当天晚上,我就写了一个小小的A ...

  4. Android实例-使用自定义字体文件(XE8+小米2)

    结果: 1.需要修改DELPHI自身的FMX.FontGlyphs.Android.pas,复制到程序的根目录下(红色部分为修改过的). 2.字体文件从 C:\Windows\Fonts 直接拷贝到A ...

  5. JAVA Eclipse开发Android程序如何自定义图标

    直接用做好的png图片替换res的所有分辨率的lc_launcher.png图片 注意到不同文件夹有不同的分辨率,直接把png图片做成最大的替换掉即可,不管小的. drawable-xxhdpi    ...

  6. RAD DELPHI XE5的android开发环境配置

    RAD XE5 支持本地化跨平台编译(IOS,OS-X,WIN 64,WIN32,ANDROID) 对于android的开发环境,XE5支持模拟器,和真机设备两种模式: 1. 模拟器:(支持4.0.3 ...

  7. Delphi XE5的Android开发平台搭建[转]

    Delphi XE5支持Android ARM的开发,可以在Android虚拟机里运行,因此建议将XE5安装在64bit的Windows,内存可以大于3GB Delphi XE5安装光盘中包含了最基本 ...

  8. Delphi XE5的Android开发平台搭建

    Delphi XE5支持Android ARM的开发,可以在Android虚拟机里运行,因此建议将XE5安装在64bit的Windows,内存可以大于3GB Delphi XE5安装光盘中包含了最基本 ...

  9. 【WP 8.1开发】如何把自定义字体塞进应用里

    或许,系统自带的字体不足以体现应用程序的魅力,对于表现极强的汉字来说,更是如此.这时候,我们就会想,要是能把网上下载的艺术字体塞到应用包中,那岂不美哉?那么,这可以实现吗?答案是Yes的. 接下来,阿 ...

随机推荐

  1. [转] 带你彻底理解RSA算法原理

    http://blog.csdn.net/dbs1215/article/details/48953589 1. 什么是RSA RSA算法是现今使用最广泛的公钥密码算法,也是号称地球上最安全的加密算法 ...

  2. selenium的webdriver三种等待方式(显式等待WebDriverWait+implicitly_wait隐式等待+sleep强制等待)

    隐式等待是等页面加载,不是等元素!!! 1.显式等待 一个显式等待是你定义的一段代码,用于等待某个条件发生然后再继续执行后续代码.显式等待是等元素加载!!! 2.隐式等待,相当于设置全局的等待,在定位 ...

  3. TrueCrypt 7.1a Hashes

    Here are the SHA256, SHA1, and MD5 hashes of all TrueCrypt version 7.1a files. The signature of the ...

  4. Vim删除文件到行首或者行尾

    vim用的不是很熟练,只是有时候需要的时候会学习一下 我们知道,vim有三种模式,一种是一般模式,一种是编辑模式,另外一种是命令行模式 在一般模式下,可以进行删除,复制粘贴等操作,在编辑模式下可以编辑 ...

  5. The 2018 ACM-ICPC Asia Qingdao Regional Contest, Online -C:Halting Problem(模拟)

    C Halting Problem In computability theory, the halting problem is the problem of determining, from a ...

  6. Linux更改yum源

    环境 centos6 阿里云镜像地址 https://opsx.alibaba.com/mirror(本篇所选) centos官网镜像 http://mirror.centos.org/ 网易云镜像地 ...

  7. Kubernetes学习

    DNS for Services and Pods Services 创建基本的Service kind: Service spec.clusterIP: 为一组相同的服务的Pod集群提供一个虚拟ip ...

  8. selected标签判断默认选中

    <select name="suggestedType" style="width:280px" > <option value=" ...

  9. 下载B站、秒拍等视频网站视频

    需要一个FVD Downloader(插件) 安装过程很简单,会浏览器安装插件的就不多说了!

  10. LeetCode–Flip Game

    You are playing the following Flip Game with your friend: Given a string that contains only these tw ...