如何在Lazarus/Delphi中判断字体是否含指定Unicode字符字形?
我明白你在Lazarus/Delphi里判断字体是否包含特定Unicode字形的需求——之前用C#、PHP的方案没法直接套用确实头疼。下面给你几个更可靠的方法,不管是原生API调用还是改进版的迂回方案都有:
一、Windows平台(Delphi/Lazarus通用):用原生API精准判断
Windows提供了直接的API来检查字体对字符的支持,这是最准确的方案,没有迂回方法的不确定性。
1. 核心方法:结合GetGlyphIndicesW和GetFontUnicodeRanges
GetGlyphIndicesW可以获取字符在字体中的字形索引,GetFontUnicodeRanges能列出字体支持的所有Unicode区间,两者结合可以避免误判(比如有些字体用默认字形替代缺失字符的情况)。
示例代码:
uses Windows, Graphics; // 检查字符是否在字体支持的Unicode区间内 function IsCharInFontUnicodeRange(ACanvas: TCanvas; AChar: WideChar): Boolean; var BufferSize: DWORD; pRanges: PUnicodeRanges; i: Integer; CharCode: DWORD; begin Result := False; CharCode := DWORD(AChar); // 获取存储字体Unicode区间所需的缓冲区大小 BufferSize := GetFontUnicodeRanges(ACanvas.Handle, nil); if BufferSize = 0 then Exit; GetMem(pRanges, BufferSize); try pRanges^.cRanges := BufferSize div SizeOf(TUnicodeRange); if GetFontUnicodeRanges(ACanvas.Handle, pRanges) = 0 then Exit; // 遍历所有区间,检查字符是否在范围内 for i := 0 to pRanges^.cRanges - 1 do begin if (CharCode >= pRanges^.ranges[i].wcLow) and (CharCode <= pRanges^.ranges[i].wcLow + pRanges^.ranges[i].cGlyphs - 1) then begin Result := True; Break; end; end; finally FreeMem(pRanges); end; end; // 最终判断:字符是否有对应的字形 function IsCharHasGlyphInFont(ACanvas: TCanvas; AChar: WideChar): Boolean; var GlyphIndex: DWORD; begin // 先确认字符在字体的Unicode支持范围内,再检查字形索引 Result := IsCharInFontUnicodeRange(ACanvas, AChar); if Result then begin // GGO_GLYPHINDEX参数表示只返回原始字形索引,不使用系统替代字符 GlyphIndex := GetGlyphIndicesW(ACanvas.Handle, PWideChar(@AChar), 1, GGO_GLYPHINDEX); Result := GlyphIndex <> 0; end; end;
使用方式:
比如要检查你的TLabel字体是否支持某个箭头字符:
var ArrowChar: WideChar; begin ArrowChar := WideChar($2192); // 右箭头字符 if IsCharHasGlyphInFont(Label1.Canvas, ArrowChar) then Label1.Caption := ArrowChar else Label1.Caption := '字体不支持该箭头'; end;
二、Lazarus跨平台方案(Linux/macOS/Windows):用Pango API
如果你的项目需要跨平台运行,Lazarus基于GTK的环境可以直接调用Pango的字体检查接口,这是Linux/macOS下的标准方案。
示例代码:
uses Pango, PangoFc, Gtk2, Graphics; function IsCharSupportedInFontPango(AFont: TFont; AChar: WideChar): Boolean; var PangoFont: PPangoFont; PangoFcFont: PPangoFcFont; FcCharSet: PFcCharSet; Context: PPangoContext; begin Result := False; // 获取当前控件的Pango上下文 Context := PangoWidgetGetContext(Widget); // Widget为你的控件实例,比如Label1.Handle转换 if not Assigned(Context) then Exit; // 获取对应字体的Pango对象 PangoFont := PangoContextGetFont(Context, nil); if Assigned(PangoFont) then begin PangoFcFont := PPangoFcFont(PangoFont); FcCharSet := PangoFcFontGetCharSet(PangoFcFont); if Assigned(FcCharSet) then begin // 检查字符是否在字体的字符集中 Result := FcCharSetHasChar(FcCharSet, DWORD(AChar)); end; g_object_unref(PangoFont); end; end;
三、改进版迂回方案(替代你当前的尺寸对比)
如果因为某些限制没法用API,你可以优化现有的方案,避免误判:
- 不用U+0001(部分字体可能对这个控制字符有特殊处理),改用几乎所有字体都不支持的生僻字符,比如U+10FFFF(Unicode最大码点)作为参照。
- 若尺寸相同,进一步做像素级对比,彻底排除“缺失字形尺寸恰好和目标字符一致”的极端情况。
示例代码:
uses Windows, Graphics; function IsCharHasGlyphWithFallback(ACanvas: TCanvas; AChar: WideChar): Boolean; const // 选择一个几乎所有字体都不支持的参照字符 UnsupportChar = WideChar($10FFFF); var TargetSize, UnsupportSize: TSize; MemDC: HDC; Bmp: HBITMAP; OldBmp: HGDIOBJ; TargetPixels, UnsupportPixels: array of Byte; begin ACanvas.Font := ACanvas.Font; // 确保字体设置生效 // 第一步:对比尺寸 GetTextExtentPoint32W(ACanvas.Handle, PWideChar(@AChar), 1, TargetSize); GetTextExtentPoint32W(ACanvas.Handle, PWideChar(@UnsupportChar), 1, UnsupportSize); Result := (TargetSize.cx <> UnsupportSize.cx) or (TargetSize.cy <> UnsupportSize.cy); // 第二步:尺寸相同时,做像素级对比 if not Result then begin MemDC := CreateCompatibleDC(ACanvas.Handle); Bmp := CreateCompatibleBitmap(ACanvas.Handle, TargetSize.cx, TargetSize.cy); OldBmp := SelectObject(MemDC, Bmp); try // 绘制目标字符并提取像素 FillRect(MemDC, Rect(0,0,TargetSize.cx,TargetSize.cy), GetStockObject(WHITE_BRUSH)); SetTextColor(MemDC, clBlack); SetBkColor(MemDC, clWhite); DrawTextW(MemDC, PWideChar(@AChar), 1, Rect(0,0,TargetSize.cx,TargetSize.cy), DT_LEFT or DT_TOP); SetLength(TargetPixels, TargetSize.cx * TargetSize.cy * 4); GetDIBits(MemDC, Bmp, 0, TargetSize.cy, @TargetPixels[0], nil, DIB_RGB_COLORS); // 绘制参照字符并提取像素 FillRect(MemDC, Rect(0,0,TargetSize.cx,TargetSize.cy), GetStockObject(WHITE_BRUSH)); DrawTextW(MemDC, PWideChar(@UnsupportChar), 1, Rect(0,0,TargetSize.cx,TargetSize.cy), DT_LEFT or DT_TOP); SetLength(UnsupportPixels, TargetSize.cx * TargetSize.cy * 4); GetDIBits(MemDC, Bmp, 0, TargetSize.cy, @UnsupportPixels[0], nil, DIB_RGB_COLORS); // 对比像素数据 Result := not CompareMem(@TargetPixels[0], @UnsupportPixels[0], Length(TargetPixels)); finally SelectObject(MemDC, OldBmp); DeleteObject(Bmp); DeleteDC(MemDC); end; end; end;
内容的提问来源于stack exchange,提问作者MMazzon
相关产品推荐
相关产品推荐

