You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

326 line
8.0 KiB

  1. unit utsTtfUtils;
  2. {$IFDEF FPC}
  3. {$mode delphi}{$H+}
  4. {$ENDIF}
  5. interface
  6. uses
  7. Classes, SysUtils;
  8. const
  9. NAME_ID_COPYRIGHT = 0;
  10. NAME_ID_FACE_NAME = 1;
  11. NAME_ID_STYLE_NAME = 2;
  12. NAME_ID_FULL_NAME = 4;
  13. function MakeTTTableName(const ch1, ch2, ch3, ch4: Char): Cardinal;
  14. function GetTTString(pBuffer: Pointer; BufferSize: Integer; NameID, LanguageID: Cardinal; var Text: AnsiString): Boolean;
  15. function GetTTFontFullNameFromStream(Stream: TStream; LanguageID: Cardinal): AnsiString;
  16. function GetTTFontFullNameFromFile(Filename: AnsiString; LanguageID: Cardinal): AnsiString;
  17. implementation
  18. uses
  19. utsUtils;
  20. type
  21. TT_OFFSET_TABLE = packed record
  22. uMajorVersion: Word;
  23. uMinorVersion: Word;
  24. uNumOfTables: Word;
  25. uSearchRange: Word;
  26. uEntrySelector: Word;
  27. uRangeShift: Word;
  28. end;
  29. TT_TABLE_DIRECTORY = packed record
  30. TableName: Cardinal; // table name
  31. uCheckSum: Cardinal; // Check sum
  32. uOffset: Cardinal; // Offset from beginning of file
  33. uLength: Cardinal; // length of the table in bytes
  34. end;
  35. TT_NAME_TABLE_HEADER = packed record
  36. uFSelector: Word; //format selector. Always 0
  37. uNRCount: Word; //Name Records count
  38. uStorageOffset: Word; //Offset for strings storage, from start of the table
  39. end;
  40. TT_NAME_RECORD = packed record
  41. uPlatformID: Word;
  42. uEncodingID: Word;
  43. uLanguageID: Word;
  44. uNameID: Word;
  45. uStringLength: Word;
  46. uStringOffset: Word; //from start of storage area
  47. end;
  48. const
  49. PLATFORM_ID_APPLE_UNICODE = 0;
  50. PLATFORM_ID_MACINTOSH = 1;
  51. PLATFORM_ID_MICROSOFT = 3;
  52. function SWAPWORD(x: Word): Word;
  53. begin
  54. Result := x and $FF;
  55. Result := Result shl 8;
  56. Result := Result or (x shr 8);
  57. end;
  58. function SWAPLONG(x: Cardinal): Cardinal;
  59. begin
  60. Result := (x and $FF) shl 24;
  61. x := x shr 8;
  62. Result := Result or ((x and $FF) shl 16);
  63. x := x shr 8;
  64. Result := Result or ((x and $FF) shl 8);
  65. x := x shr 8;
  66. Result := Result or x;
  67. end;
  68. function GetTTTableData(Stream: TStream; TableName: Cardinal; pBuff: Pointer; var Size: Integer): Boolean;
  69. var
  70. Pos: Int64;
  71. OffsetTable: TT_OFFSET_TABLE;
  72. TableDir: TT_TABLE_DIRECTORY;
  73. Idx: Integer;
  74. begin
  75. Result := False;
  76. Pos := Stream.Position;
  77. // Reading table header
  78. Stream.Read(OffsetTable, sizeof(TT_OFFSET_TABLE));
  79. OffsetTable.uNumOfTables := SWAPWORD(OffsetTable.uNumOfTables);
  80. OffsetTable.uMajorVersion := SWAPWORD(OffsetTable.uMajorVersion);
  81. OffsetTable.uMinorVersion := SWAPWORD(OffsetTable.uMinorVersion);
  82. //check is this is a true type font and the version is 1.0
  83. if (OffsetTable.uMajorVersion <> 1) or (OffsetTable.uMinorVersion <> 0) then
  84. Exit;
  85. // seaching table with name
  86. for Idx := 0 to OffsetTable.uNumOfTables -1 do begin
  87. Stream.Read(TableDir, sizeof(TT_TABLE_DIRECTORY));
  88. if (TableName = TableDir.TableName) then begin
  89. TableDir.uOffset := SWAPLONG(TableDir.uOffset);
  90. TableDir.uLength := SWAPLONG(TableDir.uLength);
  91. // copying tabledata
  92. if (pBuff <> nil) and (Size >= Integer(TableDir.uLength)) then begin
  93. Stream.Seek(TableDir.uOffset, soBeginning);
  94. Size := Stream.Read(pBuff^, TableDir.uLength);
  95. Result := Size = Integer(TableDir.uLength);
  96. end else
  97. begin
  98. // restoring streamposition
  99. Stream.Position := Pos;
  100. Size := TableDir.uLength;
  101. Result := True;
  102. end;
  103. break;
  104. end;
  105. end;
  106. end;
  107. function MakeTTTableName(const ch1, ch2, ch3, ch4: Char): Cardinal;
  108. begin
  109. Result := ord(ch4) shl 24 or ord(ch3) shl 16 or ord(ch2) shl 8 or ord(ch1);
  110. end;
  111. function GetTTString(pBuffer: Pointer; BufferSize: Integer; NameID, LanguageID: Cardinal; var Text: AnsiString): Boolean;
  112. var
  113. pActBuffer: pByte;
  114. ttNTHeader: TT_NAME_TABLE_HEADER;
  115. ttRecord: TT_NAME_RECORD;
  116. Idx: Integer;
  117. Prio: Integer;
  118. procedure ExtractName;
  119. var
  120. pTempBuffer: pByte;
  121. pTemp: pWideChar;
  122. uStringLengthH2: Word;
  123. procedure SwapText(pText: pWideChar; Length: Word);
  124. begin
  125. while Length > 0 do begin
  126. pWord(pText)^ := SWAPWORD(pWord(pText)^);
  127. Inc(pText);
  128. Dec(Length);
  129. end;
  130. end;
  131. begin
  132. Result := True;
  133. ttRecord.uStringLength := SWAPWORD(ttRecord.uStringLength);
  134. ttRecord.uStringOffset := SWAPWORD(ttRecord.uStringOffset);
  135. uStringLengthH2 := ttRecord.uStringLength shr 1;
  136. pTempBuffer := pBuffer;
  137. Inc(pTempBuffer, ttNTHeader.uStorageOffset + ttRecord.uStringOffset);
  138. // Unicode
  139. if ((ttRecord.uPlatformID = PLATFORM_ID_MICROSOFT) and (ttRecord.uEncodingID in [0, 1])) or
  140. ((ttRecord.uPlatformID = PLATFORM_ID_APPLE_UNICODE) and (ttRecord.uEncodingID > 0)) then begin
  141. pTemp := tsStrAlloc(uStringLengthH2);
  142. try
  143. // uStringLengthH2 * 2 because possible buffer overrun
  144. Move(pTempBuffer^, pTemp^, uStringLengthH2 * 2);
  145. SwapText(pTemp, uStringLengthH2);
  146. WideCharLenToStrVar(pTemp, uStringLengthH2, Text);
  147. finally
  148. tsStrDispose(pTemp);
  149. end;
  150. end else
  151. // none unicode
  152. begin
  153. SetLength(Text, ttRecord.uStringLength);
  154. Move(pTempBuffer^, Text[1], ttRecord.uStringLength);
  155. end;
  156. end;
  157. begin
  158. Result := False;
  159. pActBuffer := pBuffer;
  160. Move(pActBuffer^, ttNTHeader, sizeof(TT_NAME_TABLE_HEADER));
  161. inc(pActBuffer, sizeof(TT_NAME_TABLE_HEADER));
  162. ttNTHeader.uNRCount := SWAPWORD(ttNTHeader.uNRCount);
  163. ttNTHeader.uStorageOffset := SWAPWORD(ttNTHeader.uStorageOffset);
  164. Prio := -1;
  165. for Idx := 0 to ttNTHeader.uNRCount -1 do begin
  166. Move(pActBuffer^, ttRecord, sizeof(TT_NAME_RECORD));
  167. Inc(pActBuffer, sizeof(TT_NAME_RECORD));
  168. ttRecord.uNameID := SWAPWORD(ttRecord.uNameID);
  169. if ttRecord.uNameID = NameID then begin
  170. ttRecord.uPlatformID := SWAPWORD(ttRecord.uPlatformID);
  171. ttRecord.uEncodingID := SWAPWORD(ttRecord.uEncodingID);
  172. ttRecord.uLanguageID := SWAPWORD(ttRecord.uLanguageID);
  173. // highest priority
  174. if (ttRecord.uPlatformID = PLATFORM_ID_MICROSOFT) then begin
  175. // system language
  176. if (ttRecord.uLanguageID = languageID) then begin
  177. if Prio <= 7 then begin
  178. ExtractName;
  179. Prio := 7;
  180. end;
  181. end else
  182. // english
  183. if (ttRecord.uLanguageID = 1033) then begin
  184. if Prio <= 6 then begin
  185. ExtractName;
  186. Prio := 6;
  187. end;
  188. end else
  189. // all else
  190. if Prio <= 5 then begin
  191. ExtractName;
  192. Prio := 5;
  193. end;
  194. end else
  195. // apple unicode
  196. if (ttRecord.uPlatformID = PLATFORM_ID_APPLE_UNICODE) then begin
  197. ExtractName;
  198. Prio := 4;
  199. end else
  200. // macintosh
  201. if (ttRecord.uPlatformID = PLATFORM_ID_MACINTOSH) then begin
  202. // english
  203. if (ttRecord.uLanguageID = 0) then begin
  204. if Prio <= 3 then begin
  205. ExtractName;
  206. Prio := 3;
  207. end;
  208. end else
  209. // all other
  210. begin
  211. ExtractName;
  212. Prio := 2;
  213. end;
  214. end else
  215. begin
  216. if Prio <= 1 then begin
  217. ExtractName;
  218. Prio := 1;
  219. end;
  220. end;
  221. end;
  222. end;
  223. end;
  224. function GetTTFontFullNameFromStream(Stream: TStream; LanguageID: Cardinal): AnsiString;
  225. var
  226. TableName: Cardinal;
  227. Buffer: Pointer;
  228. BufferSize: Integer;
  229. begin
  230. TableName := MakeTTTableName('n', 'a', 'm', 'e');
  231. if GetTTTableData(Stream, TableName, nil, BufferSize) then begin
  232. GetMem(Buffer, BufferSize);
  233. try
  234. if GetTTTableData(Stream, TableName, Buffer, BufferSize) then begin
  235. if not GetTTString(Buffer, BufferSize, NAME_ID_FULL_NAME, LanguageID, Result) then
  236. if not GetTTString(Buffer, BufferSize, NAME_ID_FACE_NAME, LanguageID, Result) then
  237. Result := '';
  238. end;
  239. finally
  240. FreeMem(Buffer);
  241. end;
  242. end;
  243. end;
  244. function GetTTFontFullNameFromFile(Filename: AnsiString; LanguageID: Cardinal): AnsiString;
  245. var
  246. fs: TFileStream;
  247. begin
  248. fs := TFileStream.Create(String(Filename), fmOpenRead or fmShareDenyWrite);
  249. try
  250. result := GetTTFontFullNameFromStream(fs, LanguageID);
  251. finally
  252. fs.Free;
  253. end;
  254. end;
  255. end.