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