2025-01-10:
1.更新 fr不支持ttc文件格式,将fontutil单元的ttc删除,修改为红色代码重新编译就可以。
2.更新搜字体函数,先找精确的,找不到就模糊的。
在2024-11-18修复fr导出PDF的Bug后,只能使用指定的几种字体,总感觉不完美。
结合前几天对字体文件的研究,利用研究成果对原方案进行改进,改进后的方案已完美实现导出系统所有中文字体。
1、将fontutil.pas拷贝到\FastReport\Sources\ExportPack
注意:保存fontutil.pas时要注意文件名称的大小写,要与单元名称一致,否则在linux编译失败。
1.更新 fr不支持ttc文件格式,将fontutil单元的ttc删除,修改为红色代码重新编译就可以。
2.更新搜字体函数,先找精确的,找不到就模糊的。
在2024-11-18修复fr导出PDF的Bug后,只能使用指定的几种字体,总感觉不完美。
结合前几天对字体文件的研究,利用研究成果对原方案进行改进,改进后的方案已完美实现导出系统所有中文字体。
1、将fontutil.pas拷贝到\FastReport\Sources\ExportPack
注意:保存fontutil.pas时要注意文件名称的大小写,要与单元名称一致,否则在linux编译失败。
unit fontutil; {$mode objfpc}{$H+} interface uses {$ifdef windows}windows,{$endif} Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, FileUtil, LazFileUtils, LConvEncoding; type tagTTC_HEADER_LE = packed record tag: array[0..3] of ansichar; uMajorVersion: word; uMinorVersion: word; uNumFonts: longword; end; TableDirectoryEntry = packed record Tag: array[0..3] of ansichar; CheckSum: longword; Offset: longword; Length: longword; end; TableDirectory = packed record SfntVersion: longword; NumTables: word; SearchRange: word; EntrySelector: word; RangeShift: word; end; FontInfo = packed record copyright: string; fontFamily: string; fontSubFamily: string; fontIdentifier: string; fontName: string; fontVersion: string; postscriptName: string; trademark: string; FontType: string; flags: string; end; { TForm1 } //根据字体文件返回对应字体信息 function ReadFontName(FontFileName: string; language: integer): FontInfo; //根据字体名称查找对应是中文/英文字体名称及字体文件名和所在目录 function SearchFontName(_FontName: string;out FontNameCN, FontNameEN, FontFileName: string): string; //初始化时搜索字体列表 procedure SearchFontDirList; procedure ListDirFile(SourceDirName: string); procedure AddFontDirList(var AList: TStringList); var FontFilePath: TStringList; FontNameList: TStringList; implementation function SwapEndian(V: longword): longword; begin Result := (V shl 24) or ((V shl 8) and $00FF0000) or ((V shr 8) and $0000FF00) or (V shr 24); end; function SwapEndianWord(W: word): word; begin Result := ((W and $FF) shl 8) or ((W shr 8) and $FF); end; //根据字体文件返回对应字体信息 function ReadFontName(FontFileName: string; language: integer): FontInfo; var tagTTC: tagTTC_HEADER_LE; FileStream: TFileStream; TableDir: TableDirectory; TableDirectory: TableDirectoryEntry; i, j: integer; PlatformID, EncodingID, LanguageID, NameID, stringLength, StringOffset: word; fSelector: word; nRCount: word; storageOffset: word; offsets: word; buff: TBytes; ttcoffset: longword; now, toSkip, Positions: int64; Unicode: boolean; flags: string; function TByteToStrUnicode(b: TBytes): string; var i, o: integer; s: widestring; begin i := 0; Result := ''; while i <= length(b) - 1 do begin o := (b[i] shl 8) or (b[i + 1]); if o < 128 then s := chr(o) else s := widechar(o); Result := Result + s; Inc(i); Inc(i); end; end; function TByteToStr(b: TBytes): string; var i: integer; begin Result := ''; for i := 0 to length(b) - 1 do begin if b[i] >= 32 then Result := Result + chr(b[i]); end; end; function ReadFontInfo: string; begin stringLength := SwapEndianword(stringLength); stringOffset := SwapEndianWord(stringOffset); offsets := now + stringOffset + storageOffset; now := now + 3 * 2 + 6 * 2 * (j + 1); toSkip := offsets - now; Result := ''; if (toSkip >= 0) then begin SetLength(buff, stringLength); FileStream.Seek(toSkip, soFromCurrent); FileStream.Read(buff[0], stringLength); if Unicode then Result := TByteToStrUnicode(buff) else Result := TBytetostr(buff); buff := nil; FileStream.Seek(Positions, soBeginning); end; end; begin Result.copyright := ''; Result.fontFamily := ''; Result.fontSubFamily := ''; Result.fontIdentifier := ''; Result.fontName := ''; Result.fontVersion := ''; Result.postscriptName := ''; Result.trademark := ''; Result.FontType := ''; Result.flags := ''; if FileExists(FontFileName) then begin FileStream := TFileStream.Create(FontFileName, fmOpenRead or fmShareDenyWrite); try FileStream.Read(tagTTC, sizeof(tagTTC_HEADER_LE)); ttcoffset := 0; if tagTTC.tag = 'ttcf' then begin FileStream.Read(ttcoffset, sizeof(ttcoffset)); ttcoffset := SwapEndian(ttcoffset); FileStream.Seek((ttcoffset), soFromBeginning); end else FileStream.Seek(0, soFromBeginning); FileStream.Read(TableDir, 12); for i := 0 to SwapEndianWord(TableDir.NumTables) - 1 do begin FileStream.Read(TableDirectory, sizeof(TableDirectoryEntry)); if TableDirectory.Tag = 'name' then begin now := ttcoffset + 12 + 16 * (i + 1); toSkip := SwapEndian(TableDirectory.offset) - now; if (toSkip >= 0) then FileStream.Seek(toSkip, soFromCurrent); FileStream.Read(fSelector, 2); FileStream.Read(nRCount, 2); FileStream.Read(storageOffset, 2); //高低位转换 fSelector := SwapEndianword(fSelector); nRCount := SwapEndianword(nRCount); storageOffset := SwapEndianword(storageOffset); for j := 0 to nRCount - 1 do begin FileStream.Read(platformID, 2); FileStream.Read(encodingID, 2); FileStream.Read(languageID, 2); FileStream.Read(nameID, 2); FileStream.Read(stringLength, 2); FileStream.Read(stringOffset, 2); //高低位转换 platformID := SwapEndianword(platformID); encodingID := SwapEndianword(encodingID); languageID := SwapEndianword(languageID); nameID := SwapEndianword(nameID); Positions := FileStream.Position; Unicode := False; if (platformID = 3) and (encodingID = 1) then Unicode := True; if (languageID = $804) and ((encodingID = 0) or (encodingID = 1) or (encodingID = 3)) then Unicode := True; //if (languageID=$409) then// $409--英文 and (platformID=3) and ((encodingID=0) or (encodingID=1) or (encodingID=3)) then if (languageID = language) then//(languageID=$804)--中文 begin if (nameID = 0) then Result.copyright := ReadFontInfo; if (nameID = 1) then Result.fontFamily := ReadFontInfo; if (nameID = 2) then Result.fontSubFamily := ReadFontInfo; if (nameID = 3) then Result.fontIdentifier := ReadFontInfo; if (nameID = 4) then Result.fontName := ReadFontInfo; if (nameID = 5) then Result.fontVersion := ReadFontInfo; if (nameID = 6) then Result.postscriptName := ReadFontInfo; if (nameID = 7) then begin Result.trademark := ReadFontInfo; Break; end; end; end; break; end; end; finally FileStream.Free; end; end; end; //根据字体名称查找对应是中文/英文字体名称及字体文件名和所在目录 function SearchFontName(_FontName: string; out FontNameCN, FontNameEN, FontFileName: string): string; var f: FontInfo; fontnames: string; i: integer; FList: TStringList; function sfn(searchtype:Boolean):boolean; var res:Boolean; i:Integer; begin Result:=False; FList := TStringList.Create; for i := 0 to FontNameList.Count - 1 do begin FontFileName := FontNameList.ValueFromIndex[i]; FList.DelimitedText := FontFileName; FList.StrictDelimiter:=true;//不再将空格视为分隔符 FList.Delimiter := ','; FontNameCN := FList[0]; FontNameEN := FList[1]; FontFileName := FList[2]; if searchtype then begin if FontNameCN = _FontName then res:=true Else res:=False; end else begin if pos(_FontName,FontNameCN)>=1 then res:=True ELSE res:=false; end; if res then begin Result := True; break; end; FontNameCN := ''; FontNameEN := ''; FontFileName := ''; end; FList.Free; end; begin Result := ''; if (FontNameList = nil) and (FontFilePath <> nil) then begin //搜索所有中文字体 FontNameList := TStringList.Create; for i := 0 to FontFilePath.Count - 1 do begin FontFileName := FontFilePath.ValueFromIndex[i]; //FontFileName:= f := ReadFontName(FontFileName, $804);//中文 fontnames := f.fontName; if f.fontName <> '' then begin //搜到中文字体后再//搜索对应的英文字体 f := ReadFontName(FontFileName, $409);//英文 FontNameList.Add(fontnames + ',' + f.fontName + ',' + FontFileName); end; end; end; if FontNameList <> nil then begin if _FontName<>'' then begin //找不到完全匹配的名称,则用模糊搜 if not sfn(True) then sfn(False); if FontFileName<>'' then Result:='找到字体'; end; end; end; //初始化时搜索字体列表 procedure SearchFontDirList; var lWinFontPath: array[0..MAX_PATH] of widechar; lPasWinFontPath: string; begin if FontFilePath <> nil then FontFilePath.Free; FontFilePath := TStringList.Create; {$ifdef windows} Windows.GetWindowsDirectoryW(@lWinFontPath[0], MAX_PATH); lPasWinFontPath:=IncludeTrailingPathDelimiter(lWinFontPath) + 'Fonts' + PathDelim; ListDirFile(lPasWinFontPath); {$endif} {$ifdef linux} ListDirFile('/usr/share/fonts/'); ListDirFile(ExpandFileName('~/')+'.local/share/fonts'); ListDirFile(GetUserDir + '.fonts/'); {$endif} end; procedure ListDirFile(SourceDirName: string); var i, j: integer; FilesList: TStringList; SourceDirectoryAndFileName, SubDirStructure, FinalisedFileName: string; SourceDir: string; begin SourceDir := SourceDirName; SubDirStructure := ''; SetCurrentDir(SourceDirName); FilesList := FindAllFiles(SourceDirName, '*', True); try for i := 0 to FilesList.Count - 1 do begin SourceDirectoryAndFileName := ChompPathDelim(CleanAndExpandDirectory(FilesList.Strings[i])); SubDirStructure := IncludeTrailingPathDelimiter( ExtractFileDir(SourceDirectoryAndFileName)); if SourceDir + '/' = SubDirStructure then SubDirStructure := ''; j := pos(SourceDir, SubDirStructure) + length(SourceDir); if pos(SourceDir, SubDirStructure) > 0 then SubDirStructure := Copy(SubDirStructure, j, length(SubDirStructure)); FinalisedFileName := ExtractFileName(FilesList.Strings[i]); // if (pos('.ttf', SourceDirectoryAndFileName.ToLower) > 0) or // (pos('.ttc', SourceDirectoryAndFileName.ToLower) > 0) then if (pos('.ttf', SourceDirectoryAndFileName.ToLower) > 0) then begin FontFilePath.Add(FilesList.Strings[i]); end; end; finally FilesList.Free; end; end; procedure AddFontDirList(var AList: TStringList); var FontNameCN, FontNameEN, FontFileName, Res: string; i: integer; FList : TStringList; begin SearchFontName('', FontNameCN, FontNameEN, FontFileName); FList := TStringList.Create; if FontNameList <> nil then for i := 0 to FontNameList.Count - 1 do begin FList.DelimitedText := FontNameList[i]; FList.StrictDelimiter:=true;//不再将空格视为分隔符 FList.Delimiter := ','; if AList.IndexOf(ExtractFilePath(FList[2]))=-1 then AList.Add(ExtractFilePath(FList[2])); end; FList.Free; end; initialization SearchFontDirList; finalization if FontFilePath <> nil then FontFilePath.Free; if FontNameList<> nil then FontNameList.Free; end.
在银河麒麟导出的PDF Demo:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~