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.

910 lines
29 KiB

  1. unit utsFontCreatorGDI;
  2. {$IFDEF FPC}
  3. {$mode objfpc}{$H+}
  4. {$ENDIF}
  5. interface
  6. uses
  7. Classes, SysUtils,
  8. utsFont, utsFontCreator, utsTypes, utsGDI, utsImage, utsContext;
  9. type
  10. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  11. TtsFontGDI = class(TtsFont)
  12. private
  13. fHandle: THandle;
  14. fMat2: TMat2;
  15. function GetGlyphIndex(const aCharCode: WideChar): Integer;
  16. procedure GetCharImageAANone(const aDC: HDC; const aCharCode: WideChar; const aImage: TtsImage; const aFormat: TtsFormat);
  17. procedure GetCharImageAANormal(const aDC: HDC; const aCharCode: WideChar; const aImage: TtsImage; const aFormat: TtsFormat);
  18. protected
  19. {%H-}constructor Create(const aHandle: THandle; const aCreator: TtsFontCreator; const aMetric: TtsFontMetric; const aNames: TtsFontNames);
  20. public
  21. procedure GetCharImage(const aCharCode: WideChar; const aCharImage: TtsImage; const aFormat: TtsFormat); override;
  22. function GetGlyphMetrics(const aCharCode: WideChar; out aGlyphOrigin, aGlyphSize: TtsPosition; out aAdvance: Integer): Boolean; override;
  23. destructor Destroy; override;
  24. end;
  25. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  26. TtsFontRegistration = class(TObject)
  27. protected
  28. fIsRegistered: Boolean;
  29. fFontname: String;
  30. procedure UnregisterFont; virtual; abstract;
  31. public
  32. property IsRegistered: Boolean read fIsRegistered;
  33. property Fontname: String read fFontname;
  34. destructor Destroy; override;
  35. end;
  36. TtsFontRegistrationFile = class(TtsFontRegistration)
  37. private
  38. fFilename: String;
  39. protected
  40. procedure UnregisterFont; override;
  41. public
  42. constructor Create(const aFilename: String);
  43. end;
  44. TtsFontRegistrationStream = class(TtsFontRegistration)
  45. private
  46. fHandle: THandle;
  47. protected
  48. procedure UnregisterFont; override;
  49. public
  50. constructor Create(const aStream: TStream);
  51. end;
  52. TtsRegistredFontGDI = class(TtsFontGDI)
  53. private
  54. fRegistration: TtsFontRegistration;
  55. protected
  56. {%H-}constructor Create(const aRegistration: TtsFontRegistration; const aHandle: THandle;
  57. const aCreator: TtsFontCreator; const aMetric: TtsFontMetric; const aNames: TtsFontNames);
  58. public
  59. destructor Destroy; override;
  60. end;
  61. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  62. TtsFontCreatorGDI = class(TtsFontCreator)
  63. private
  64. function CreateFont(const aFontname: String; const aSize: Integer; const aStyle: TtsFontStyles; const aAntiAliasing: TtsAntiAliasing; out aMetric: TtsFontMetric; out aNames: TtsFontNames): THandle;
  65. public
  66. function GetFontByName(const aFontname: String; const aSize: Integer; const aStyle: TtsFontStyles; const aAntiAliasing: TtsAntiAliasing): TtsFont; overload; override;
  67. function GetFontByFile(const aFilename: String; const aSize: Integer; const aStyle: TtsFontStyles; const aAntiAliasing: TtsAntiAliasing): TtsFont; overload; override;
  68. function GetFontByStream(const aStream: TStream; const aSize: Integer; const aStyle: TtsFontStyles; const aAntiAliasing: TtsAntiAliasing): TtsFont; overload; override;
  69. constructor Create(const aContext: TtsContext);
  70. destructor Destroy; override;
  71. end;
  72. implementation
  73. uses
  74. Math,
  75. utsUtils;
  76. type
  77. TT_OFFSET_TABLE = packed record
  78. uMajorVersion: Word;
  79. uMinorVersion: Word;
  80. uNumOfTables: Word;
  81. uSearchRange: Word;
  82. uEntrySelector: Word;
  83. uRangeShift: Word;
  84. end;
  85. TT_TABLE_DIRECTORY = packed record
  86. TableName: Cardinal; // table name
  87. uCheckSum: Cardinal; // Check sum
  88. uOffset: Cardinal; // Offset from beginning of file
  89. uLength: Cardinal; // length of the table in bytes
  90. end;
  91. TT_NAME_TABLE_HEADER = packed record
  92. uFSelector: Word; //format selector. Always 0
  93. uNRCount: Word; //Name Records count
  94. uStorageOffset: Word; //Offset for strings storage, from start of the table
  95. end;
  96. TT_NAME_RECORD = packed record
  97. uPlatformID: Word;
  98. uEncodingID: Word;
  99. uLanguageID: Word;
  100. uNameID: Word;
  101. uStringLength: Word;
  102. uStringOffset: Word; //from start of storage area
  103. end;
  104. const
  105. NAME_ID_COPYRIGHT = 0;
  106. NAME_ID_FACE_NAME = 1;
  107. NAME_ID_STYLE_NAME = 2;
  108. NAME_ID_FULL_NAME = 4;
  109. PLATFORM_ID_APPLE_UNICODE = 0;
  110. PLATFORM_ID_MACINTOSH = 1;
  111. PLATFORM_ID_MICROSOFT = 3;
  112. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  113. //TTF Utils/////////////////////////////////////////////////////////////////////////////////////////////////////////////
  114. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  115. function SWAPWORD(x: Word): Word;
  116. begin
  117. Result := x and $FF;
  118. Result := Result shl 8;
  119. Result := Result or (x shr 8);
  120. end;
  121. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  122. function SWAPLONG(x: Cardinal): Cardinal;
  123. begin
  124. Result := (x and $FF) shl 24;
  125. x := x shr 8;
  126. Result := Result or ((x and $FF) shl 16);
  127. x := x shr 8;
  128. Result := Result or ((x and $FF) shl 8);
  129. x := x shr 8;
  130. Result := Result or x;
  131. end;
  132. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  133. function GetTTTableData(Stream: TStream; TableName: Cardinal; pBuff: Pointer; var Size: Integer): Boolean;
  134. var
  135. Pos: Int64;
  136. OffsetTable: TT_OFFSET_TABLE;
  137. TableDir: TT_TABLE_DIRECTORY;
  138. Idx: Integer;
  139. begin
  140. Result := False;
  141. Pos := Stream.Position;
  142. // Reading table header
  143. Stream.Read(OffsetTable{%H-}, sizeof(TT_OFFSET_TABLE));
  144. OffsetTable.uNumOfTables := SWAPWORD(OffsetTable.uNumOfTables);
  145. OffsetTable.uMajorVersion := SWAPWORD(OffsetTable.uMajorVersion);
  146. OffsetTable.uMinorVersion := SWAPWORD(OffsetTable.uMinorVersion);
  147. //check is this is a true type font and the version is 1.0
  148. if (OffsetTable.uMajorVersion <> 1) or (OffsetTable.uMinorVersion <> 0) then
  149. Exit;
  150. // seaching table with name
  151. for Idx := 0 to OffsetTable.uNumOfTables -1 do begin
  152. Stream.Read(TableDir{%H-}, sizeof(TT_TABLE_DIRECTORY));
  153. if (TableName = TableDir.TableName) then begin
  154. TableDir.uOffset := SWAPLONG(TableDir.uOffset);
  155. TableDir.uLength := SWAPLONG(TableDir.uLength);
  156. // copying tabledata
  157. if (pBuff <> nil) and (Size >= Integer(TableDir.uLength)) then begin
  158. Stream.Seek(TableDir.uOffset, soBeginning);
  159. Size := Stream.Read(pBuff^, TableDir.uLength);
  160. Result := (Size = Integer(TableDir.uLength));
  161. end else
  162. begin
  163. // restoring streamposition
  164. Stream.Position := Pos;
  165. Size := TableDir.uLength;
  166. Result := True;
  167. end;
  168. break;
  169. end;
  170. end;
  171. end;
  172. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  173. function MakeTTTableName(const ch1, ch2, ch3, ch4: Char): Cardinal;
  174. begin
  175. Result := ord(ch4) shl 24 or ord(ch3) shl 16 or ord(ch2) shl 8 or ord(ch1);
  176. end;
  177. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  178. function GetTTString(pBuffer: Pointer; BufferSize: Integer; NameID, LanguageID: Cardinal; out Text: String): Boolean;
  179. var
  180. pActBuffer: pByte;
  181. ttNTHeader: TT_NAME_TABLE_HEADER;
  182. ttRecord: TT_NAME_RECORD;
  183. Idx: Integer;
  184. Prio: Integer;
  185. procedure ExtractName;
  186. var
  187. pTempBuffer: pByte;
  188. pTemp: pWideChar;
  189. uStringLengthH2: Word;
  190. procedure SwapText(pText: pWideChar; Length: Word);
  191. begin
  192. while Length > 0 do begin
  193. pWord(pText)^ := SWAPWORD(pWord(pText)^);
  194. Inc(pText);
  195. Dec(Length);
  196. end;
  197. end;
  198. begin
  199. Result := True;
  200. ttRecord.uStringLength := SWAPWORD(ttRecord.uStringLength);
  201. ttRecord.uStringOffset := SWAPWORD(ttRecord.uStringOffset);
  202. uStringLengthH2 := ttRecord.uStringLength shr 1;
  203. pTempBuffer := pBuffer;
  204. Inc(pTempBuffer, ttNTHeader.uStorageOffset + ttRecord.uStringOffset);
  205. // Unicode
  206. if ((ttRecord.uPlatformID = PLATFORM_ID_MICROSOFT) and (ttRecord.uEncodingID in [0, 1])) or
  207. ((ttRecord.uPlatformID = PLATFORM_ID_APPLE_UNICODE) and (ttRecord.uEncodingID > 0)) then begin
  208. pTemp := tsStrAlloc(uStringLengthH2);
  209. try
  210. // uStringLengthH2 * 2 because possible buffer overrun
  211. Move(pTempBuffer^, pTemp^, uStringLengthH2 * 2);
  212. SwapText(pTemp, uStringLengthH2);
  213. WideCharLenToStrVar(pTemp, uStringLengthH2, Text);
  214. finally
  215. tsStrDispose(pTemp);
  216. end;
  217. end else
  218. // none unicode
  219. begin
  220. SetLength(Text, ttRecord.uStringLength);
  221. Move(pTempBuffer^, Text[1], ttRecord.uStringLength);
  222. end;
  223. end;
  224. begin
  225. Result := False;
  226. pActBuffer := pBuffer;
  227. Move(pActBuffer^, ttNTHeader{%H-}, sizeof(TT_NAME_TABLE_HEADER));
  228. inc(pActBuffer, sizeof(TT_NAME_TABLE_HEADER));
  229. ttNTHeader.uNRCount := SWAPWORD(ttNTHeader.uNRCount);
  230. ttNTHeader.uStorageOffset := SWAPWORD(ttNTHeader.uStorageOffset);
  231. Prio := -1;
  232. for Idx := 0 to ttNTHeader.uNRCount -1 do begin
  233. Move(pActBuffer^, ttRecord, sizeof(TT_NAME_RECORD));
  234. Inc(pActBuffer, sizeof(TT_NAME_RECORD));
  235. ttRecord.uNameID := SWAPWORD(ttRecord.uNameID);
  236. if ttRecord.uNameID = NameID then begin
  237. ttRecord.uPlatformID := SWAPWORD(ttRecord.uPlatformID);
  238. ttRecord.uEncodingID := SWAPWORD(ttRecord.uEncodingID);
  239. ttRecord.uLanguageID := SWAPWORD(ttRecord.uLanguageID);
  240. // highest priority
  241. if (ttRecord.uPlatformID = PLATFORM_ID_MICROSOFT) then begin
  242. // system language
  243. if (ttRecord.uLanguageID = languageID) then begin
  244. if Prio <= 7 then begin
  245. ExtractName;
  246. Prio := 7;
  247. end;
  248. end else
  249. // english
  250. if (ttRecord.uLanguageID = 1033) then begin
  251. if Prio <= 6 then begin
  252. ExtractName;
  253. Prio := 6;
  254. end;
  255. end else
  256. // all else
  257. if Prio <= 5 then begin
  258. ExtractName;
  259. Prio := 5;
  260. end;
  261. end else
  262. // apple unicode
  263. if (ttRecord.uPlatformID = PLATFORM_ID_APPLE_UNICODE) then begin
  264. ExtractName;
  265. Prio := 4;
  266. end else
  267. // macintosh
  268. if (ttRecord.uPlatformID = PLATFORM_ID_MACINTOSH) then begin
  269. // english
  270. if (ttRecord.uLanguageID = 0) then begin
  271. if Prio <= 3 then begin
  272. ExtractName;
  273. Prio := 3;
  274. end;
  275. end else
  276. // all other
  277. begin
  278. ExtractName;
  279. Prio := 2;
  280. end;
  281. end else
  282. begin
  283. if Prio <= 1 then begin
  284. ExtractName;
  285. Prio := 1;
  286. end;
  287. end;
  288. end;
  289. end;
  290. end;
  291. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  292. function GetTTFontFullNameFromStream(Stream: TStream; LanguageID: Cardinal): String;
  293. var
  294. TableName: Cardinal;
  295. Buffer: Pointer;
  296. BufferSize: Integer;
  297. begin
  298. TableName := MakeTTTableName('n', 'a', 'm', 'e');
  299. BufferSize := 0;
  300. if GetTTTableData(Stream, TableName, nil, BufferSize) then begin
  301. GetMem(Buffer, BufferSize);
  302. try
  303. if GetTTTableData(Stream, TableName, Buffer, BufferSize) then begin
  304. if not GetTTString(Buffer, BufferSize, NAME_ID_FULL_NAME, LanguageID, Result) then
  305. if not GetTTString(Buffer, BufferSize, NAME_ID_FACE_NAME, LanguageID, Result) then
  306. Result := '';
  307. end;
  308. finally
  309. FreeMem(Buffer);
  310. end;
  311. end;
  312. end;
  313. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  314. function GetTTFontFullNameFromFile(const aFilename: String; const aLanguageID: Cardinal): String;
  315. var
  316. fs: TFileStream;
  317. begin
  318. fs := TFileStream.Create(aFilename, fmOpenRead or fmShareDenyWrite);
  319. try
  320. result := GetTTFontFullNameFromStream(fs, aLanguageID);
  321. finally
  322. fs.Free;
  323. end;
  324. end;
  325. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  326. //TtsFontGDI////////////////////////////////////////////////////////////////////////////////////////////////////////////
  327. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  328. function TtsFontGDI.GetGlyphIndex(const aCharCode: WideChar): Integer;
  329. var
  330. DC: HDC;
  331. GCPRes: TGCPResultsW;
  332. begin
  333. result := -1;
  334. DC := CreateCompatibleDC(0);
  335. try
  336. SelectObject(DC, fHandle);
  337. if Assigned(GetCharacterPlacementW) then begin
  338. FillChar(GCPRes{%H-}, SizeOf(GCPRes), #0);
  339. GetMem(GCPRes.lpGlyphs, SizeOf(Cardinal));
  340. try
  341. GCPRes.lStructSize := SizeOf(GCPRes);
  342. GCPRes.lpGlyphs^ := 0;
  343. GCPRes.nGlyphs := 1;
  344. if (GetCharacterPlacementW(DC, @aCharCode, 1, GCP_MAXEXTENT, @GCPRes, 0) <> GDI_ERROR) and
  345. (GCPRes.nGlyphs = 1) and
  346. (GCPRes.lpGlyphs <> nil) then
  347. begin
  348. result := GCPRes.lpGlyphs^;
  349. end;
  350. finally
  351. FreeMem(GCPRes.lpGlyphs);
  352. end;
  353. end;
  354. finally
  355. DeleteDC(DC);
  356. end;
  357. end;
  358. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  359. procedure TtsFontGDI.GetCharImageAANone(const aDC: HDC; const aCharCode: WideChar; const aImage: TtsImage; const aFormat: TtsFormat);
  360. var
  361. gm: TGlyphMetrics;
  362. GlyphIndex, srcW, srcX, w, h, x, y: Integer;
  363. Size, OutlineRes: Cardinal;
  364. Buffer, pSrc, pDst: PByte;
  365. procedure ExpandByte;
  366. var
  367. i, cnt, srcCnt: Integer;
  368. c: TtsColor4f;
  369. begin
  370. srcCnt := min(8, srcX);
  371. cnt := min(8, x);
  372. for i := 1 to cnt do begin
  373. c := tsColor4f(1, 1, 1, 1);
  374. if ((pSrc^ and $80) > 0) then
  375. c.a := 1.0
  376. else
  377. c.a := 0.0;
  378. pSrc^ := (pSrc^ and not $80) shl 1;
  379. tsFormatMap(aFormat, pDst, c);
  380. end;
  381. dec(srcX, srcCnt);
  382. dec(x, cnt);
  383. inc(pSrc);
  384. end;
  385. begin
  386. if (fMat2.eM11.value <> 1) then
  387. raise EtsException.Create('invalid value');
  388. FillChar(gm{%H-}, SizeOf(gm), #0);
  389. GlyphIndex := GetGlyphIndex(aCharCode);
  390. if (GlyphIndex < 0) then
  391. exit;
  392. Size := GetGlyphOutlineA(aDC, GlyphIndex, GGO_BITMAP or GGO_GLYPH_INDEX, @gm, 0, nil, @fMat2);
  393. if (Size = GDI_ERROR) or (Size = 0) then
  394. exit;
  395. GetMem(Buffer, Size);
  396. try
  397. OutlineRes := GetGlyphOutlineA(aDC, GlyphIndex, GGO_BITMAP or GGO_GLYPH_INDEX, @gm, Size, Buffer, @fMat2);
  398. if (OutlineRes = GDI_ERROR) then
  399. exit;
  400. w := gm.gmBlackBoxX;
  401. h := gm.gmBlackBoxY;
  402. srcW := (Integer(Size) div h) * 8;
  403. if (w <= 0) or (h <= 0) then
  404. exit;
  405. aImage.CreateEmpty(aFormat, w, h);
  406. pSrc := Buffer;
  407. for y := 0 to h-1 do begin
  408. pDst := aImage.Scanline[y];
  409. srcX := srcW;
  410. x := w;
  411. while (srcX > 0) do
  412. ExpandByte;
  413. end;
  414. finally
  415. Freemem(Buffer);
  416. end;
  417. end;
  418. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  419. procedure TtsFontGDI.GetCharImageAANormal(const aDC: HDC; const aCharCode: WideChar; const aImage: TtsImage; const aFormat: TtsFormat);
  420. var
  421. gm: TGlyphMetrics;
  422. OutlineRes: DWORD;
  423. GlyphIndex, tmp, Spacer, x, y, w, h: Integer;
  424. Size: Cardinal;
  425. Buffer, pSrc, pDst: PByte;
  426. procedure CopyPixel;
  427. var
  428. i: Integer;
  429. tmp, cnt: Cardinal;
  430. c: TtsColor4f;
  431. begin
  432. cnt := min(x, fMat2.eM11.value);
  433. tmp := 0;
  434. for i := 0 to cnt-1 do begin
  435. tmp := tmp + pSrc^;
  436. inc(pSrc, 1);
  437. end;
  438. dec(x, cnt);
  439. c := tsColor4f(1, 1, 1, tmp / $40);
  440. tsFormatMap(aFormat, pDst, c);
  441. end;
  442. begin
  443. FillChar(gm{%H-}, SizeOf(gm), #0);
  444. GlyphIndex := GetGlyphIndex(aCharCode);
  445. if (GlyphIndex < 0) then
  446. exit;
  447. Size := GetGlyphOutlineA(aDC, GlyphIndex, GGO_GRAY8_BITMAP or GGO_GLYPH_INDEX, @gm, 0, nil, @fMat2);
  448. if (Size = GDI_ERROR) or (Size = 0) then
  449. exit;
  450. GetMem(Buffer, Size);
  451. try
  452. OutlineRes := GetGlyphOutlineA(aDC, GlyphIndex, GGO_GRAY8_BITMAP or GGO_GLYPH_INDEX, @gm, Size, Buffer, @fMat2);
  453. if (OutlineRes = GDI_ERROR) then
  454. exit;
  455. w := Integer(gm.gmBlackBoxX) div fMat2.eM11.value;
  456. h := gm.gmBlackBoxY;
  457. tmp := Integer(gm.gmBlackBoxX) mod fMat2.eM11.value;
  458. if (tmp <> 0) then
  459. w := w + fMat2.eM11.value - tmp;
  460. if (w <= 0) or (h <= 0) then
  461. exit;
  462. // spacer
  463. Spacer := gm.gmBlackBoxX mod 4;
  464. if (Spacer <> 0) then
  465. Spacer := 4 - Spacer;
  466. // copy image
  467. aImage.CreateEmpty(aFormat, w, h);
  468. pSrc := Buffer;
  469. for y := 0 to h-1 do begin
  470. pDst := aImage.Scanline[y];
  471. x := gm.gmBlackBoxX;
  472. while (x > 0) do
  473. CopyPixel;
  474. inc(pSrc, Spacer);
  475. end;
  476. finally
  477. FreeMem(Buffer);
  478. end;
  479. end;
  480. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  481. constructor TtsFontGDI.Create(const aHandle: THandle; const aCreator: TtsFontCreator; const aMetric: TtsFontMetric; const aNames: TtsFontNames);
  482. begin
  483. inherited Create(aCreator, aMetric, aNames);
  484. FillChar(fMat2, SizeOf(fMat2), #0);
  485. fMat2.eM11.value := 1;
  486. fMat2.eM22.value := 1;
  487. fHandle := aHandle;
  488. end;
  489. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  490. procedure TtsFontGDI.GetCharImage(const aCharCode: WideChar; const aCharImage: TtsImage; const aFormat: TtsFormat);
  491. var
  492. DC: HDC;
  493. begin
  494. DC := CreateCompatibleDC(0);
  495. try
  496. SelectObject(DC, fHandle);
  497. case Metric.AntiAliasing of
  498. tsAANone:
  499. GetCharImageAANone(DC, aCharCode, aCharImage, aFormat);
  500. tsAANormal:
  501. GetCharImageAANormal(DC, aCharCode, aCharImage, aFormat);
  502. end;
  503. finally
  504. DeleteDC(DC);
  505. end;
  506. end;
  507. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  508. function TtsFontGDI.GetGlyphMetrics(const aCharCode: WideChar; out aGlyphOrigin, aGlyphSize: TtsPosition; out aAdvance: Integer): Boolean;
  509. var
  510. GlyphIndex: Integer;
  511. DC: HDC;
  512. gm: TGlyphMetrics;
  513. Size: Cardinal;
  514. begin
  515. result := false;
  516. aGlyphOrigin.x := 0;
  517. aGlyphOrigin.x := 0;
  518. aGlyphSize.x := 0;
  519. aGlyphSize.y := 0;
  520. aAdvance := 0;
  521. GlyphIndex := GetGlyphIndex(aCharCode);
  522. if (GlyphIndex < 0) then
  523. exit;
  524. DC := CreateCompatibleDC(0);
  525. try
  526. SelectObject(DC, fHandle);
  527. case Metric.AntiAliasing of
  528. tsAANone: begin
  529. Size := GetGlyphOutlineA(DC, GlyphIndex, GGO_BITMAP or GGO_GLYPH_INDEX, @gm, 0, nil, @fMat2);
  530. end;
  531. tsAANormal: begin
  532. Size := GetGlyphOutlineA(DC, GlyphIndex, GGO_GRAY8_BITMAP or GGO_GLYPH_INDEX, @gm, 0, nil, @fMat2);
  533. end;
  534. else
  535. Size := GDI_ERROR;
  536. end;
  537. if (Size = GDI_ERROR) then
  538. Size := GetGlyphOutlineA(DC, GlyphIndex, GGO_METRICS or GGO_GLYPH_INDEX, @gm, 0, nil, @fMat2);
  539. if (Size <> GDI_ERROR) then begin
  540. aGlyphOrigin.x := gm.gmptGlyphOrigin.x;
  541. aGlyphOrigin.y := gm.gmptGlyphOrigin.y;
  542. aGlyphSize.x := gm.gmBlackBoxX;
  543. aGlyphSize.y := gm.gmBlackBoxY;
  544. aAdvance := gm.gmCellIncX;
  545. result := true;
  546. end;
  547. finally
  548. DeleteDC(DC);
  549. end;
  550. end;
  551. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  552. destructor TtsFontGDI.Destroy;
  553. begin
  554. DeleteObject(fHandle);
  555. inherited Destroy;
  556. end;
  557. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  558. //TtsFontRegistration///////////////////////////////////////////////////////////////////////////////////////////////////
  559. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  560. destructor TtsFontRegistration.Destroy;
  561. begin
  562. if fIsRegistered then
  563. UnregisterFont;
  564. inherited Destroy;
  565. end;
  566. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  567. //TtsFontRegistrationFile///////////////////////////////////////////////////////////////////////////////////////////////
  568. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  569. procedure TtsFontRegistrationFile.UnregisterFont;
  570. begin
  571. if Assigned(RemoveFontResourceExA) then
  572. RemoveFontResourceExA(PAnsiChar(AnsiString(fFilename)), 0, nil)
  573. else if Assigned(RemoveFontResourceA) then
  574. RemoveFontResourceA(PAnsiChar(AnsiString(fFilename)));
  575. end;
  576. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  577. constructor TtsFontRegistrationFile.Create(const aFilename: String);
  578. var
  579. lang: AnsiString;
  580. begin
  581. inherited Create;
  582. fFilename := aFilename;
  583. // get Fontname
  584. SetLength(lang, 4);
  585. GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_ILANGUAGE, @lang[1], 4);
  586. fFontname := GetTTFontFullNameFromFile(aFilename, StrToInt('$' + String(lang)));
  587. // register font
  588. if Assigned(AddFontResourceExA) then
  589. fIsRegistered := (AddFontResourceExA(PAnsiChar(AnsiString(fFilename)), 0, nil) > 0)
  590. else if Assigned(AddFontResourceA) then
  591. fIsRegistered := (AddFontResourceA(PAnsiChar(AnsiString(fFilename))) > 0)
  592. else
  593. fIsRegistered := false;
  594. end;
  595. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  596. //TtsFontRegistrationStream/////////////////////////////////////////////////////////////////////////////////////////////
  597. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  598. procedure TtsFontRegistrationStream.UnregisterFont;
  599. begin
  600. if Assigned(RemoveFontMemResourceEx) then
  601. RemoveFontMemResourceEx(fHandle);
  602. end;
  603. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  604. constructor TtsFontRegistrationStream.Create(const aStream: TStream);
  605. var
  606. lang: AnsiString;
  607. ms: TMemoryStream;
  608. cnt: DWORD;
  609. begin
  610. inherited Create;
  611. fHandle := 0;
  612. fIsRegistered := false;
  613. // get Fontname
  614. SetLength(Lang, 4);
  615. GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_ILANGUAGE, @lang[1], 4);
  616. fFontname := GetTTFontFullNameFromStream(aStream, StrToInt('$' + String(Lang)));
  617. // register font
  618. ms := TMemoryStream.Create;
  619. try
  620. ms.CopyFrom(aStream, 0);
  621. if Assigned(AddFontMemResourceEx) then
  622. fHandle := AddFontMemResourceEx(ms.Memory, ms.Size, nil, @cnt);
  623. fIsRegistered := (fHandle > 0);
  624. finally
  625. FreeAndNil(ms);
  626. end;
  627. end;
  628. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  629. //TtsRegistredFontGDI///////////////////////////////////////////////////////////////////////////////////////////////////
  630. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  631. constructor TtsRegistredFontGDI.Create(const aRegistration: TtsFontRegistration; const aHandle: THandle;
  632. const aCreator: TtsFontCreator; const aMetric: TtsFontMetric; const aNames: TtsFontNames);
  633. begin
  634. inherited Create(aHandle, aCreator, aMetric, aNames);
  635. fRegistration := aRegistration;
  636. end;
  637. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  638. destructor TtsRegistredFontGDI.Destroy;
  639. begin
  640. FreeAndNil(fRegistration);
  641. inherited Destroy;
  642. end;
  643. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  644. //TtsFontCreatorGDI/////////////////////////////////////////////////////////////////////////////////////////////////////
  645. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  646. function TtsFontCreatorGDI.CreateFont(const aFontname: String; const aSize: Integer; const aStyle: TtsFontStyles;
  647. const aAntiAliasing: TtsAntiAliasing; out aMetric: TtsFontMetric; out aNames: TtsFontNames): THandle;
  648. var
  649. LogFont: TLogFontA;
  650. i: Integer;
  651. DC: HDC;
  652. TableName, BufSize: Cardinal;
  653. Buffer: PByte;
  654. Lang, tmpName: AnsiString;
  655. TextMetric: TTextMetricW;
  656. OutlineMetric: TOutlineTextmetricW;
  657. function _(e: Boolean; a, b: Integer): Integer;
  658. begin
  659. if e then
  660. result := a
  661. else
  662. result := b;
  663. end;
  664. begin
  665. FillChar(aMetric{%H-}, SizeOf(aMetric), #0);
  666. aMetric.Size := aSize;
  667. aMetric.Style := aStyle;
  668. aMetric.AntiAliasing := aAntiAliasing;
  669. aNames.Fontname := aFontname;
  670. // prepare font attribs
  671. FillChar(LogFont{%H-}, SizeOf(LogFont), #0);
  672. tmpName := AnsiString(aFontname);
  673. for i := 1 to min(Length(aFontname), Length(LogFont.lfFaceName)) do
  674. LogFont.lfFaceName[i-1] := tmpName[i];
  675. LogFont.lfCharSet := DEFAULT_CHARSET;
  676. LogFont.lfHeight := -aSize;
  677. LogFont.lfWeight := _(tsStyleBold in aStyle, FW_BOLD, FW_NORMAL);
  678. LogFont.lfItalic := _(tsStyleItalic in aStyle, 1, 0);
  679. LogFont.lfUnderline := _(tsStyleUnderline in aStyle, 1, 0);
  680. LogFont.lfQuality := _(aAntiAliasing = tsAANormal, ANTIALIASED_QUALITY, NONANTIALIASED_QUALITY);
  681. result := CreateFontIndirectA(LogFont);
  682. DC := CreateCompatibleDC(0);
  683. try try
  684. SelectObject(DC, result);
  685. TableName := MakeTTTableName('n', 'a', 'm', 'e');
  686. BufSize := GetFontData(DC, TableName, 0, nil, 0);
  687. if (BufSize <> GDI_ERROR) then begin
  688. GetMem(Buffer, BufSize);
  689. try
  690. if (GetFontData(DC, TableName, 0, Buffer, BufSize) <> GDI_ERROR) then begin
  691. SetLength(Lang, 4);
  692. GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_ILANGUAGE, @Lang[1], 4);
  693. GetTTString(Buffer, BufSize, NAME_ID_COPYRIGHT, StrToInt('$' + String(Lang)), aNames.Copyright);
  694. GetTTString(Buffer, BufSize, NAME_ID_FACE_NAME, StrToInt('$' + String(Lang)), aNames.FaceName);
  695. GetTTString(Buffer, BufSize, NAME_ID_STYLE_NAME, StrToInt('$' + String(Lang)), aNames.StyleName);
  696. GetTTString(Buffer, BufSize, NAME_ID_FULL_NAME, StrToInt('$' + String(Lang)), aNames.FullName);
  697. end;
  698. finally
  699. FreeMem(Buffer);
  700. end;
  701. end;
  702. if GetTextMetricsW(DC, TextMetric{%H-}) then begin
  703. aMetric.Ascent := TextMetric.tmAscent;
  704. aMetric.Descent := TextMetric.tmDescent;
  705. aMetric.ExternalLeading := TextMetric.tmExternalLeading;
  706. aMetric.DefaultChar := TextMetric.tmDefaultChar;
  707. end;
  708. if (GetOutlineTextMetricsW(DC, SizeOf(OutlineMetric), OutlineMetric{%H-}) > 0) then begin
  709. aMetric.UnderlinePos := OutlineMetric.otmsUnderscorePosition;
  710. aMetric.UnderlineSize := Min(1, OutlineMetric.otmsUnderscoreSize);
  711. aMetric.StrikeoutPos := OutlineMetric.otmsStrikeoutPosition;
  712. aMetric.StrikeoutSize := Min(1, OutlineMetric.otmsStrikeoutSize);
  713. end;
  714. except
  715. DeleteObject(result);
  716. result := 0;
  717. end;
  718. finally
  719. DeleteDC(DC);
  720. end;
  721. end;
  722. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  723. function TtsFontCreatorGDI.GetFontByName(const aFontname: String; const aSize: Integer; const aStyle: TtsFontStyles; const aAntiAliasing: TtsAntiAliasing): TtsFont;
  724. var
  725. handle: THandle;
  726. metric: TtsFontMetric;
  727. names: TtsFontNames;
  728. begin
  729. handle := CreateFont(aFontname, aSize, aStyle, aAntiAliasing, metric, names);
  730. if (handle = 0) then
  731. raise EtsException.Create('unable to create font from name: ' + aFontname);
  732. result := TtsFontGDI.Create(handle, self, metric, names);
  733. end;
  734. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  735. function TtsFontCreatorGDI.GetFontByFile(const aFilename: String; const aSize: Integer; const aStyle: TtsFontStyles; const aAntiAliasing: TtsAntiAliasing): TtsFont;
  736. var
  737. reg: TtsFontRegistrationFile;
  738. handle: THandle;
  739. metric: TtsFontMetric;
  740. names: TtsFontNames;
  741. begin
  742. reg := TtsFontRegistrationFile.Create(aFilename);
  743. try
  744. if not reg.IsRegistered then
  745. raise EtsException.Create('unable to register font file: ' + aFilename);
  746. handle := CreateFont(reg.Fontname, aSize, aStyle, aAntiAliasing, metric, names);
  747. if (handle = 0) then
  748. raise EtsException.Create('unable to create font from file: ' + aFilename);
  749. except
  750. FreeAndNil(reg);
  751. raise;
  752. end;
  753. result := TtsRegistredFontGDI.Create(reg, handle, self, metric, names);
  754. end;
  755. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  756. function TtsFontCreatorGDI.GetFontByStream(const aStream: TStream; const aSize: Integer; const aStyle: TtsFontStyles; const aAntiAliasing: TtsAntiAliasing): TtsFont;
  757. var
  758. reg: TtsFontRegistrationStream;
  759. handle: THandle;
  760. metric: TtsFontMetric;
  761. names: TtsFontNames;
  762. begin
  763. reg := TtsFontRegistrationStream.Create(aStream);
  764. if not reg.IsRegistered then
  765. raise EtsException.Create('unable to register font from stream');
  766. handle := CreateFont(reg.Fontname, aSize, aStyle, aAntiAliasing, metric, names);
  767. if (handle = 0) then
  768. raise EtsException.Create('unable to create font from stream: ' + reg.Fontname);
  769. result := TtsRegistredFontGDI.Create(reg, handle, self, metric, names);
  770. end;
  771. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  772. constructor TtsFontCreatorGDI.Create(const aContext: TtsContext);
  773. begin
  774. inherited Create(aContext);
  775. InitGDI;
  776. end;
  777. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  778. destructor TtsFontCreatorGDI.Destroy;
  779. begin
  780. inherited Destroy; // first free all fonts (managed by parent class)
  781. QuitGDI;
  782. end;
  783. end.