GLS.WindowsFont.pas 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. //
  2. // The graphics engine GLScene
  3. //
  4. unit GLS.WindowsFont;
  5. (* TFont Import into a BitmapFont using variable width...*)
  6. interface
  7. {$I Stage.Defines.inc}
  8. uses
  9. Winapi.OpenGL,
  10. Winapi.Windows,
  11. System.Classes,
  12. System.Math,
  13. System.SysUtils,
  14. System.Types,
  15. System.UITypes,
  16. VCL.Graphics,
  17. GLS.Scene,
  18. GLS.Texture,
  19. GLS.BitmapFont,
  20. GLS.RenderContextInfo,
  21. GLS.VectorLists,
  22. Stage.Utils,
  23. Stage.VectorGeometry,
  24. Stage.OpenGLTokens,
  25. GLS.ApplicationFileIO,
  26. Stage.VectorTypes;
  27. type
  28. (* A bitmap font automatically built from a TFont.
  29. It works like a TGLBitmapfont, you set ranges and which chars are assigned
  30. to which indexes, however here you also set the Font property to any TFont
  31. available to the system and it renders in GLScene as close to that font
  32. as posible, on some font types this is 100% on some a slight difference
  33. in spacing can occur at most 1 pixel per char on some char combinations.
  34. Ranges must be sorted in ascending ASCII order and should not overlap.
  35. As the font texture is automatically layed out, the Ranges StartGlyphIdx
  36. property is ignored and replaced appropriately. *)
  37. TGLWindowsBitmapFont = class(TGLCustomBitmapFont)
  38. private
  39. FFont: TFont;
  40. procedure SetList(const AList : TGLIntegerList);
  41. protected
  42. procedure SetFont(value: TFont);
  43. procedure LoadWindowsFont; virtual;
  44. function StoreRanges: Boolean;
  45. procedure PrepareImage(var ARci: TGLRenderContextInfo); override;
  46. function TextureFormat: Integer; override;
  47. procedure StreamlineRanges;
  48. public
  49. constructor Create(AOwner: TComponent); override;
  50. destructor Destroy; override;
  51. procedure NotifyChange(Sender: TObject); override;
  52. function FontTextureWidth: Integer;
  53. function FontTextureHeight: Integer;
  54. procedure EnsureString(const s : String); overload;
  55. procedure EnsureChars(const AStart, AEnd: widechar);
  56. property Glyphs;
  57. published
  58. (* The font used to prepare the texture.
  59. Note: the font color is ignored. *)
  60. property Font: TFont read FFont write SetFont;
  61. property HSpace;
  62. property VSpace;
  63. property MagFilter;
  64. property MinFilter;
  65. property Ranges stored StoreRanges;
  66. end;
  67. implementation // ------------------------------------------------------------
  68. const
  69. cDefaultLast = '}';
  70. var
  71. Win32PlatformIsUnicode : Boolean;
  72. // ------------------
  73. // ------------------ TGLWindowsBitmapFont ------------------
  74. // ------------------
  75. constructor TGLWindowsBitmapFont.Create(AOwner: TComponent);
  76. begin
  77. inherited;
  78. FFont := TFont.Create;
  79. FFont.Color := clWhite;
  80. FFont.OnChange := NotifyChange;
  81. GlyphsAlpha := tiaAlphaFromIntensity;
  82. EnsureChars(' ', cDefaultLast);
  83. end;
  84. destructor TGLWindowsBitmapFont.Destroy;
  85. begin
  86. FFont.Free;
  87. Ranges.Clear;
  88. inherited;
  89. end;
  90. function TGLWindowsBitmapFont.FontTextureWidth: Integer;
  91. begin
  92. Result := Glyphs.Width;
  93. end;
  94. function TGLWindowsBitmapFont.FontTextureHeight: Integer;
  95. begin
  96. Result := Glyphs.Height;
  97. end;
  98. procedure TGLWindowsBitmapFont.SetFont(value: TFont);
  99. begin
  100. FFont.Assign(value);
  101. end;
  102. procedure TGLWindowsBitmapFont.NotifyChange(Sender: TObject);
  103. begin
  104. StreamlineRanges;
  105. FreeTextureHandle;
  106. InvalidateUsers;
  107. inherited;
  108. end;
  109. procedure TGLWindowsBitmapFont.LoadWindowsFont;
  110. procedure ComputeCharRects(bitmap: TBitmap);
  111. var
  112. px, py, cw, n, x, y: Integer;
  113. PaddedHeight : integer;
  114. buffer : array[0..2] of WideChar;
  115. p : PCharInfo;
  116. r : TRect;
  117. begin
  118. buffer[1] := WideChar(#32);
  119. buffer[2] := WideChar(#0);
  120. PaddedHeight:= CharHeight + GlyphsIntervalY;
  121. x := bitmap.Width; y := bitmap.Height;
  122. px := 0;
  123. py := 0;
  124. if y < CharHeight then px := x;
  125. p := @FChars[0];
  126. for n := 0 to CharacterCount - 1 do
  127. begin
  128. cw := p.w;
  129. if cw > 0 then
  130. begin
  131. Inc(cw, GlyphsIntervalX);
  132. if px + cw > x then
  133. begin
  134. px := 0;
  135. Inc(py, PaddedHeight);
  136. if py + PaddedHeight > y then
  137. begin
  138. py := bitmap.Height;
  139. y := py + TextureHeight;
  140. bitmap.Height := y;
  141. with bitmap.Canvas do
  142. begin
  143. Brush.Style := bsSolid;
  144. Brush.Color := clBlack;
  145. FillRect(Rect(0, py, x, y));
  146. end;
  147. end;
  148. end;
  149. if Assigned(bitmap) then
  150. begin
  151. //+1 makes right align (padding left);
  152. // I prefer padding right for compatibility with bitmap font...
  153. p.l := px;
  154. //should make it consistent, same as above
  155. p.t := py;
  156. r.Left := px;
  157. r.Top := py;
  158. r.Right := px + cw;
  159. r.Bottom := py + PaddedHeight;
  160. buffer[0] := TileIndexToChar(n);
  161. // Draw the Char, the trailing space is to properly handle the italics.
  162. // credits to the Unicode version of SynEdit for this function call. GPL/MPL as GLScene
  163. ExtTextOutW(bitmap.Canvas.Handle, p.l, p.t, ETO_CLIPPED, @r, buffer, 1, nil);
  164. end;
  165. Inc(px, cw);
  166. end
  167. else
  168. begin
  169. p.l := 0;
  170. p.t := 0;
  171. end;
  172. inc(p);
  173. end;
  174. end;
  175. // credits to the Unicode version of SynEdit for this function. GPL/MPL as GLScene
  176. function GetTextSize(DC: HDC; Str: PWideChar; Count: Integer): TSize;
  177. var tm: TTextMetric;
  178. begin
  179. Result.cx := 0;
  180. Result.cy := 0;
  181. GetTextExtentPoint32W(DC, Str, Count, Result);
  182. if not Win32PlatformIsUnicode then
  183. begin
  184. GetTextMetricsW(DC, tm);
  185. if tm.tmPitchAndFamily and TMPF_TRUETYPE <> 0 then
  186. Result.cx := Result.cx - tm.tmOverhang
  187. else
  188. Result.cx := tm.tmAveCharWidth * Count;
  189. end;
  190. end;
  191. var
  192. bitmap: TBitmap;
  193. ch: WideChar;
  194. i, cw, nbChars, n: Integer;
  195. begin
  196. InvalidateUsers;
  197. Glyphs.OnChange := nil;
  198. //accessing Bitmap might trigger onchange
  199. bitmap := Glyphs.Bitmap;
  200. bitmap.Height := 0;
  201. //due to lazarus doesn't properly support pixel formats
  202. bitmap.PixelFormat := pf32bit;
  203. with bitmap.Canvas do
  204. begin
  205. Font := Self.Font;
  206. Font.Color := clWhite;
  207. // get characters dimensions for the font
  208. // character size without padding; paddings are used from GlyphsInterval
  209. CharWidth := Round(MaxInteger(TextWidth('M'), TextWidth('W'), TextWidth('_')));
  210. CharHeight := TextHeight('"_pI|,');
  211. // used for padding
  212. GlyphsIntervalX := 1;
  213. GlyphsIntervalY := 1;
  214. if fsItalic in Font.Style then
  215. begin
  216. // italics aren't properly acknowledged in font width
  217. HSpaceFix := -(CharWidth div 3);
  218. CharWidth := CharWidth - HSpaceFix;
  219. end
  220. else
  221. HSpaceFix := 0;
  222. end;
  223. nbChars := CharacterCount;
  224. // Retrieve width of all characters (texture width)
  225. ResetCharWidths(0);
  226. n := 0;
  227. for i := 0 to nbChars - 1 do
  228. begin
  229. ch := TileIndexToChar(i);
  230. cw := GetTextSize(bitmap.Canvas.Handle, @ch, 1).cx-HSpaceFix;
  231. n := n + cw + GlyphsIntervalX;
  232. SetCharWidths(i, cw);
  233. end;
  234. //try to make best guess...
  235. //~total pixels, including some waste (10%)
  236. n := n * (CharHeight + GlyphsIntervalY) * 11 div 10;
  237. TextureWidth := min(512, RoundUpToPowerOf2(Round(Sqrt(n))));
  238. TextureHeight := min(512, RoundUpToPowerOf2(n div TextureWidth));
  239. bitmap.Width := TextureWidth;
  240. ComputeCharRects(bitmap);
  241. FCharsLoaded := true;
  242. Glyphs.OnChange := OnGlyphsChanged;
  243. end;
  244. function TGLWindowsBitmapFont.StoreRanges: Boolean;
  245. begin
  246. Result := (Ranges.Count <> 1) or (Ranges[0].StartASCII[1] <> ' ') or (Ranges[0].StopASCII[1] <> cDefaultLast);
  247. end;
  248. type
  249. TFriendlyRange = class(TGLBitmapFontRange);
  250. procedure TGLWindowsBitmapFont.StreamlineRanges;
  251. var
  252. I, C: Integer;
  253. begin
  254. C := 0;
  255. for I := 0 to Ranges.Count - 1 do
  256. begin
  257. TFriendlyRange(Ranges[I]).FStartGlyphIdx := C;
  258. Inc(C, Ranges[I].CharCount);
  259. TFriendlyRange(Ranges[I]).FStopGlyphIdx := MaxInteger(C - 1, 0);
  260. end;
  261. end;
  262. procedure TGLWindowsBitmapFont.SetList(const AList: TGLIntegerList);
  263. var
  264. i : integer;
  265. f, n, s : integer;
  266. begin
  267. //add existing ranges
  268. for I := 0 to Ranges.Count - 1 do
  269. with Ranges.Items[I] do
  270. AList.AddSerie(integer(StartASCII[1]), 1, CharCount);
  271. AList.SortAndRemoveDuplicates;
  272. Ranges.Clear;
  273. Ranges.BeginUpdate;
  274. if AList.Count > 0 then
  275. begin
  276. i := 0;
  277. while (i < AList.Count) and (AList[i] < 32) do inc(i);
  278. while i < AList.Count do
  279. begin
  280. f := AList[i]; n := f; s := Ranges.CharacterCount;
  281. while (i < AList.Count) and (n = AList[i]) do
  282. begin
  283. inc(i);
  284. inc(n);
  285. end;
  286. Ranges.Add(widechar(f), widechar(pred(n))).StartGlyphIdx := s;
  287. end;
  288. end;
  289. Ranges.EndUpdate;
  290. TextureChanged;
  291. InvalidateUsers;
  292. end;
  293. //add characters to internal list
  294. procedure TGLWindowsBitmapFont.EnsureChars(const AStart, AEnd: widechar);
  295. var
  296. c : WideChar;
  297. ACharList : TGLIntegerList;
  298. begin
  299. ACharList := TGLIntegerList.Create;
  300. for c := AStart to AEnd do
  301. ACharList.Add(integer(c));
  302. SetList(ACharList);
  303. ACharList.Free;
  304. end;
  305. //add characters to internal list
  306. procedure TGLWindowsBitmapFont.EnsureString(const s: String);
  307. var
  308. i : Integer;
  309. ACharList : TGLIntegerList;
  310. begin
  311. ACharList := TGLIntegerList.Create;
  312. for i := 1 to Length(s) do
  313. ACharList.Add(integer(s[i]));
  314. SetList(ACharList);
  315. ACharList.Free;
  316. end;
  317. procedure TGLWindowsBitmapFont.PrepareImage(var ARci: TGLRenderContextInfo);
  318. begin
  319. LoadWindowsFont;
  320. inherited PrepareImage(ARci);
  321. end;
  322. function TGLWindowsBitmapFont.TextureFormat: Integer;
  323. begin
  324. Result := GL_ALPHA;
  325. end;
  326. initialization // ------------------------------------------------------------
  327. Win32PlatformIsUnicode := (Win32Platform = VER_PLATFORM_WIN32_NT);
  328. RegisterClasses([TGLWindowsBitmapFont]);
  329. // ---------------------------------------------------------------------------
  330. end.