ZenGl中文字体渲染解决方案。

时间:2022-07-13 08:12:03
//HGE 中文显示新方案
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//Author - 微妙的平衡(BOGY)
//Mail - bogy.cn@gmail.com
//Home - http://bogy.cn
//Porting to ZenGl by 一路随云
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
unit GfxFont;

interface
uses
Windows, {HGE, HGESprite,}zgl_main,zgl_textures,zgl_sprite_2d,zgl_types,zgl_fx,Vcl.Graphics;

const
Font_Count = High(Word);
type
tagEngineFontGlyph = record
// t: ITexture; //纹理
t: ZglPTexture;
w: Single;
h: Single;
x: Single;
y: Single;
c: Single;
end;
TENGINEFONTGLYPH = tagEngineFontGlyph;

TGfxFont = class
private
procedure Print(X: Single; Y: Single; Text: PWideChar);
function GetTextSize(Text: PWideChar): TSize;
public
constructor Create(const FontName: PWideChar; FaceSize: Integer; bBold: Boolean = False;
bItalic: Boolean = False; bAntialias: Boolean = True);
destructor Destroy(); override;
public
// 渲染文本
procedure TextOut(x,y:Single;Text:string);
// 设置与获取颜色
procedure SetColor(Color: Cardinal);overload;
Procedure SetColor(Color:TColor);overload;
function GetColor: Cardinal;
// 获取文本宽高
Function TextWidth(Text:String):Integer;
Function TextHeight(Text:String):Integer;
// 根据坐标获取字符
function GetCharacterFromPos(Text: PWideChar; Pixel_X, Pixel_Y: Single): WideChar;
// 设置字间距
procedure SetKerningWidth(Kerning: Single);
procedure SetKerningHeight(Kerning: Single);
// 获取字间距
function GetKerningWidth(): Single;
function GetKerningHeight(): Single;
// 字体大小
function GetFontSize(): Single;
private
m_Glyphs: array [0..Font_Count-1] of TENGINEFONTGLYPH;
m_nAntialias: Cardinal;//反锯齿
m_nAscent: Integer;//基线
//m_dwFontColor: Cardinal;
m_nFontSize: Single;
m_nKerningWidth: Single;
m_nKerningHeight: Single;
//m_pHGE: IHGE; //Zengl不需要
//m_pSprite: IHGESprite;
m_pSprite:ZglPTexture;
// GDI设备
m_hMemDC: HDC;
m_hFont: HFONT;
function GetGlyphByCharacter(C: WideChar): Cardinal;
function GetWidthFromCharacter(C: WideChar; Original: Boolean = False): Single;
procedure CacheCharacter(Idx: Cardinal; C: WideChar);
end;
function RGBA(const R, G, B, A: Byte): Longword; inline;
implementation

{ TGfxFont }

const
g_byAlphaLever: array [0..65 - 1] of Byte =
(
0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48,
52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96, 100,
104,108,112,116,120,124,128,132,136,140,144,148,152,
156,160,164,168,172,176,180,184,188,192,196,200,204,
208,212,216,220,224,228,232,236,240,244,248,252,255
);

const
WideNull = WideChar(#0);
WideCR = WideChar(#13);
WideLF = WideChar(#10);
WideCRLF : WideString = #13#10;

type
TByteAry = array [0..0] of Byte;
PByteAry = ^TByteAry;
procedure TGfxFont.CacheCharacter(Idx: Cardinal; C: WideChar);
var
nChar: Cardinal;
mat: MAT2;
gm: GLYPHMETRICS;
nLen: Cardinal;
// hTex: ITexture; //纹理
hTex:zglPTexture;
lpBuf: PByteAry;
lpSrc: PByteAry;
lpDst: PLongWord;
lpTexData:PByteArray;
nSrcPitch, nDstPitch: Cardinal;
x, y, k, i,W,H: Cardinal;
begin
if (Idx < Font_Count) and (m_Glyphs[Idx].t = nil) then
begin
nChar := Cardinal(C);
mat.eM11.fract := 0; mat.eM11.value := 1;
mat.eM12.fract := 0; mat.eM12.value := 0;
mat.eM21.fract := 0; mat.eM21.value := 0;
mat.eM22.fract := 0; mat.eM22.value := 1;
nLen := GetGlyphOutlineW(m_hMemDC, nChar, m_nAntialias, gm, 0, nil, mat);
hTex := tex_CreateZero( gm.gmBlackBoxX, gm.gmBlackBoxY,$FFFFFFFF);
//hTex := m_pHGE.Texture_Create(gm.gmBlackBoxX, gm.gmBlackBoxY); //创建一个纹理用存放字体
if hTex = nil then Exit;

if nLen > 0 then
begin
GetMem(lpBuf, nLen);
if nLen = GetGlyphOutlineW(m_hMemDC, nChar, m_nAntialias, gm, nLen, lpBuf,
mat) then

begin
lpSrc := lpBuf;
//lpDst := m_pHGE.Texture_Lock(hTex, False);
//这里其实不需要GetData因为内部是空的啊
{这句是为了提高能效而代替上面的。没必要因为读取一个空纹理而从显存内从新拷贝一次数据}
GetMem( lpDst, Round( htex.Width / htex.U ) * Round( htex.Height / htex.V ) * 4 );
lpTexData:=Pointer(lpDst);

if GGO_BITMAP = m_nAntialias then
begin
//此为BITMAP抗锯齿模式
if gm.gmBlackBoxX mod 32 = 0 then
nSrcPitch := (gm.gmBlackBoxX div 32)*4
else
nSrcPitch := ((gm.gmBlackBoxX div 32) + 1)*4;
//nDstPitch := m_pHGE.Texture_GetWidth(hTex);//返回创建的纹理宽度
nDstPitch := hTex.Width;
H:=gm.gmBlackBoxY;
W:=gm.gmBlackBoxX;
for y := 0 to gm.gmBlackBoxY - 1 do
begin
x := 0;
while x < gm.gmBlackBoxX do
begin
for k := 0 to 7 do
begin
i := 8*x + k;
if i >= gm.gmBlackBoxX then
begin
Inc(x, 7);
Break;
end;
//这里同样需要镜像左右像素
if (lpSrc[x] shr (7 - k)) and 1 = 0 then
PCardinal(Cardinal(lpDst) + (W-i-1) * SizeOf(Integer))^ := 0
else
PCardinal(Cardinal(lpDst) + (W-i-1) * SizeOf(Integer))^ := $FFFFFFFF;
end;
Inc(x);
end;
Inc(lpSrc, nSrcPitch);
Inc(lpDst, nDstPitch);
end;
end
else
begin
if gm.gmBlackBoxX mod 4 = 0 then
nSrcPitch := (gm.gmBlackBoxX div 4)*4
else
nSrcPitch := (gm.gmBlackBoxX div 4 + 1)*4;

//nDstPitch := m_pHGE.Texture_GetWidth(hTex);
nDstPitch := hTex.Width;
//可能DX和OpenGL的纹理顺序不同,如果源代码不加改动 绘制出来的字
//是左右镜像了 并且旋转了180度 ,旋转的问题 我可以旋转着绘制。
//但是镜像只能交换左右位置像素来解决
H:=gm.gmBlackBoxY;
W:=gm.gmBlackBoxX;
for y := 0 to gm.gmBlackBoxY - 1 do
begin
for x := 0 to gm.gmBlackBoxX - 1 do

PCardinal(Cardinal(lpDst) + (W-X-1) * SizeOf(Integer))^ :=RGBA($FF,$FF,$FF,g_byAlphaLever[lpSrc[x]]);
//ARGB(g_byAlphaLever[lpSrc[x]], $FF, $FF, $FF);
Inc(lpSrc, nSrcPitch);
Inc(lpDst, nDstPitch);
end;
end;
tex_SetData(hTex,lpTexData,0,0,gm.gmBlackBoxX,gm.gmBlackBoxY);
FreeMem(lpTexData);
//m_pHGE.Texture_Unlock(hTex); //解锁纹理
end;
FreeMem(lpBuf);
end
else
begin
// 非正常显示字符
end;
m_Glyphs[Idx].t := hTex;
m_Glyphs[Idx].w := gm.gmBlackBoxX;
m_Glyphs[Idx].h := gm.gmBlackBoxY;
m_Glyphs[Idx].x := -gm.gmptGlyphOrigin.X;
m_Glyphs[Idx].y := gm.gmptGlyphOrigin.Y - m_nAscent;
m_Glyphs[Idx].c := gm.gmCellIncX;
end;
end;

constructor TGfxFont.Create(const FontName: PWideChar; FaceSize: Integer;
bBold, bItalic, bAntialias: Boolean);
var
h_DC: HDC;
Bold: Integer;
tm: TEXTMETRICW;
begin
// m_pHGE := HGECreate(HGE_VERSION);
// 创建GDI相关设备
// h_DC := GetDC(m_pHGE.System_GetState(HGE_HWND));//在窗口句柄上创建DC
h_DC := GetDC(zgl_Get(WINDOW_HANDLE));
m_hMemDC := CreateCompatibleDC(h_DC);
if m_hMemDC = 0 then Exit;

ReleaseDC(zgl_Get(WINDOW_HANDLE), h_DC); //释放窗口句柄
SetMapMode(m_hMemDC, MM_TEXT);
SetTextColor(m_hMemDC, RGB($FF, $FF, $FF)); //颜色
SetBkColor(m_hMemDC, RGB(0, 0, 0));

if bBold then
Bold := FW_BOLD
else
Bold := FW_NORMAL;
m_hFont := CreateFontW(
-FaceSize,
0,
0,
0,
Bold,
Integer(bItalic),
Cardinal(False),
Cardinal(False),
DEFAULT_CHARSET,
OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY,
FF_DONTCARE or DEFAULT_PITCH,
FontName);
if m_hFont = 0 then Exit;

SelectObject(m_hMemDC, m_hFont);
FillChar(m_Glyphs, SizeOf(TENGINEFONTGLYPH)*Font_Count, 0);

if bAntialias then
m_nAntialias := GGO_GRAY8_BITMAP
else
m_nAntialias := GGO_BITMAP;
GetTextMetricsW(m_hMemDC, tm);
m_nAscent := tm.tmAscent;
m_nFontSize := FaceSize;
m_nKerningWidth := 0;
m_nKerningHeight := 0;
SetColor($FFFFFFFF);//设置默认颜色为黑色
//m_pSprite := THGESprite.Create(nil, 0, 0, 0, 0); //创建一个精灵 用来绘制字体
// m_pSprite.SetColor(ARGB($FF, $FF, $FF, $FF)); // 白色透明
//m_pSprite:=tex_CreateZero(0,0,$FFFFFF);
//这里主要是建立一个空纹理 然后给与顶点数据。但是zgl这里给颜色是不需要的
end;

destructor TGfxFont.Destroy;
var
nIdx: Integer;
begin
for nIdx := 0 to Font_Count - 1 do
if m_Glyphs[nIdx].t <> nil then
m_Glyphs[nIdx].t := nil;

if m_hFont <> 0 then
DeleteObject(m_hFont);
if m_hMemDC <> 0 then
DeleteDC(m_hMemDC);
// if m_pSprite <> nil then m_pSprite := nil;
if m_pSprite <> nil then tex_Del(m_pSprite);

// if m_pHGE <> nil then m_pHGE := nil; //ZenGL不需要
inherited;
end;

function TGfxFont.GetCharacterFromPos(Text: PWideChar; Pixel_X,
Pixel_Y: Single): WideChar;
var
X, Y, W: Single;
begin
X := 0; Y := 0;
while Text^ <> WideNull do
begin
if (Text^ = WideCR) and (PWideChar(Integer(Text)+SizeOf(WideChar))^ = WideLF) then
begin
X := 0;
Y := m_nFontSize + m_nKerningWidth;
Inc(Text);
if Text^ = WideNull then
Break;
end;
W := GetWidthFromCharacter(Text^);
if (Pixel_X > X) and (Pixel_X <= X + W) and
(Pixel_Y > Y) and (Pixel_Y <= Y + m_nFontSize) then
begin
Result := Text^;
Exit;
end;
X := X + W + m_nKerningWidth;
Inc(Text);
end;
Result := WideNull;
end;

function TGfxFont.GetColor: Cardinal;
var
R,G,B,A:Byte;
begin
//Result := m_pSprite.GetColor(i);//返回颜色顶点颜色
R:=fx2dColor[0];
G:=fx2dColor[1];
B:=fx2dColor[2];
A:=fx2dColor[3];
Result:=RGBA(R,G,B,A);
end;

function TGfxFont.GetFontSize: Single;
begin
Result := m_nFontSize;
end;

function TGfxFont.GetGlyphByCharacter(C: WideChar): Cardinal;
var
Idx: Cardinal;
begin
Idx := Cardinal(C);
if m_Glyphs[Idx].t = nil then
CacheCharacter(Idx, C);
Result := Idx;
end;

function TGfxFont.GetKerningHeight: Single;
begin
Result := m_nKerningHeight;
end;

function TGfxFont.GetKerningWidth: Single;
begin
Result := m_nKerningWidth;
end;

function TGfxFont.GetTextSize(Text: PWideChar): TSize;
var
Dim: TSize;
nRowWidth: Single;
begin
nRowWidth := 0;
Dim.cx := 0;
Dim.cy := Round(m_nFontSize);
while Text^ <> WideNull do
begin
if (Text^ = WideCR) and (PWideChar(Integer(Text)+SizeOf(WideChar))^ = WideLF) then
begin
Dim.cy := Round(m_nFontSize + m_nKerningHeight);
if Dim.cx < nRowWidth then
Dim.cx := Round(nRowWidth);
nRowWidth := 0;
Inc(Text, 2);
end
else
begin
nRowWidth := nRowWidth + GetWidthFromCharacter(Text^) + m_nKerningWidth;
Inc(Text);
end;
end;
if Dim.cx < Round(nRowWidth) then
Dim.cx := Round(nRowWidth);
Result := Dim;
end;

function TGfxFont.GetWidthFromCharacter(C: WideChar;
Original: Boolean): Single;
var
Idx: Cardinal;
begin
Idx := GetGlyphByCharacter(C);
if Original and (Idx > 0) and (Idx < Font_Count) then
begin
Result := m_Glyphs[Idx].c;
Exit;
end;
if Idx >= $2000 then
Result := m_nFontSize
else
Result := m_nFontSize/2;
end;

procedure TGfxFont.TextOut(x, y: Single; Text: string);
var
lx,ly:Single;
begin
if Text='' then exit;
lx:=x;
ly:=y;
Print(lx,ly,PWideChar(Text));
end;
procedure TGfxFont.Print(X, Y: Single; Text: PWideChar);
var
OffsetX, OffsetY: Single;
Idx: Cardinal;
begin
OffsetX := X; OffsetY := Y;
while Text^ <> WideNull do
begin
if (Text^ = WideCR) and (PWideChar(Integer(Text)+SizeOf(WideChar))^ = WideLF) then
begin
OffsetX := X;
OffsetY := OffsetY + m_nFontSize + m_nKerningHeight;
Inc(Text, 2);
end
else
begin
Idx := GetGlyphByCharacter(Text^);
if Idx > 0 then
begin
//设置纹理
//m_pSprite.SetTexture(m_Glyphs[Idx].t);
m_pSprite:=m_Glyphs[Idx].t;
//设置纹理范围(应该是按照上面设置的纹理的区域),因为是直接赋值所以这里是不需要的
//m_pSprite.SetTextureRect(0, 0, m_Glyphs[Idx].w, m_Glyphs[Idx].h);
//绘制出来,
//m_pSprite.Render(OffsetX - m_Glyphs[Idx].x, OffsetY - m_Glyphs[Idx].y);
ssprite2d_Draw(m_pSprite,
OffsetX - m_Glyphs[Idx].x,OffsetY - m_Glyphs[Idx].y,
m_Glyphs[Idx].w, m_Glyphs[Idx].h,180,fx2dColor[3],FX_BLEND or FX_COLOR);
OffsetX := OffsetX + GetWidthFromCharacter(Text^) + m_nKerningWidth;
end
else
OffsetX := OffsetX + GetWidthFromCharacter(Text^) + m_nKerningWidth;

Inc(Text);
end;
end;
end;

procedure TGfxFont.SetColor(Color: Cardinal);
var
R,G,B,A:Byte;
begin
//分解颜色到RGBA分量。
R:=Color shr 24;
G:=Color shr 16;
B:=Color shr 8;
A:=Color and $FF;
//给颜色会混合赋值
fx2dColor[0]:=R;
fx2dColor[1]:=G;
fx2dColor[2]:=B;
fx2dColor[3]:=A;
//m_pSprite.SetColor(Color, i); //设置顶点颜色,如果I为-1那么就设置四个顶点颜色为color
end;

procedure TGfxFont.SetColor(Color: TColor);
var
R,G,B,A:Byte;
begin
//TColor颜色顺序是BGR
B:=Color shr 16;
G:=Color shr 8;
R:=Color and $FF;
A:=$FF;
fx2dColor[0]:=R;
fx2dColor[1]:=G;
fx2dColor[2]:=B;
fx2dColor[3]:=A;
end;

procedure TGfxFont.SetKerningHeight(Kerning: Single);
begin
m_nKerningHeight := Kerning;
end;

procedure TGfxFont.SetKerningWidth(Kerning: Single);
begin
m_nKerningWidth := Kerning;
end;

function TGfxFont.TextHeight(Text: String): Integer;
begin
Result:=GetTextSize(PWideChar(Text)).cy;
end;

function TGfxFont.TextWidth(Text: String): Integer;
begin
Result:=GetTextSize(PWideChar(Text)).cx;
end;

function RGBA(const R, G, B, A: Byte): Longword; inline;
begin
Result := (A shl 24) or (R shl 16) or (G shl 8) or B;
end;
end.