万事大吉,只欠根据字体文件(.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. 地理空间数据云--TM影像下载

    实验要用到遥感影像,,TM,,之前是可以在美国USGS上下载的,但是要FQ了,有点麻烦,, 想到之前本科实在地理空间数据云平台下载的,就试了一下以前的账号,完美!,, TM数据很丰富,到2017年的都 ...

  2. table中head表头固定,body滚动

    <style type="text/css"> .table-head { background-color: #; color: #; } .table-body { ...

  3. builtroot 添加git 下载方式

    1.buildroot/Config.in 配置default git server eg:config xxxx_GIT_SITE string "git site" defau ...

  4. DG增量恢复

    本篇文档: 讲述DG环境出现GAP归档缝隙,且主库的该归档日志被删除的情况下,快速恢复DG的连通性 流程讲述: >明确主库增量备份起点 scn 查询备库控制文件current scn ,及v$d ...

  5. pytorch使用tensorboardX进行loss可视化

    最近pytorch出了visdom,也没有怎么去研究它,主要是觉得tensorboardX已经够用,而且用起来也十分的简单 pip install tensorboardX 然后在代码里导入 from ...

  6. SEO:网站优化内容

    一.内部优化 (1)meta标签优化:例如:TDK等的优化:        首页:网站名称 或者 网站名称_提供服务介绍or产品介绍 .        频道页:频道名称_网站名称.        文章 ...

  7. Java中如何正确的将byte[]数组转化为String类型?

    很多人在编程时,总是喜欢用一下方法将数组转为字符串:(a为byte数组) String s=a.toString(); 可是每次返回的时候,新手看来返回的结果是乱码,比如说我,写RSA算法时,没有注意 ...

  8. HPU1460: 杨八方的表面兄弟

    题目描述 如果你之前关注过HPUOJ的话,那么你一定听说过杨八方的名字.在去年,很多同学共同见证了杨八方同学的填报志愿.来到学校.军训--或许你曾陪同杨八方一起思考过许多问题,又或者你是刚听说这个名字 ...

  9. 初识Odoo的辅助核算

    Odoo财务里类似辅助核算功能的叫做:Analytic Accouting,翻译为,分析会计. 再说说辅助核算是个什么东东. 财务辅助核算就是基于会计科目和会计理论分析财务数据的辅助工具. 简单的说就 ...

  10. 【HDOJ1384】【差分约束+SPFA】

    http://acm.hdu.edu.cn/showproblem.php?pid=1384 Intervals Time Limit: 10000/5000 MS (Java/Others)     ...