您的位置:首页 > 编程语言 > Delphi

Delphi XE5开发Android程序使用自定义字体文件.

2013-11-09 11:48 501 查看
万事大吉,只欠根据字体文件(.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.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐