万事大吉,只欠根据字体文件(.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 .
|