最近在做一个电子书阅读器的Android小程序来验证Delphi XE5的移动开发功能.
万事大吉,只欠根据字体文件(.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
;
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.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.
来源:http://www.raysoftware.cn/?p=475