GLS.WindowsFont.pas 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. //
  2. // The multimedia graphics platform GLScene https://github.com/glscene
  3. //
  4. unit GLS.WindowsFont;
  5. (* TFont Import into a BitmapFont using variable width...*)
  6. interface
  7. {$I GLScene.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. GLS.Utils,
  23. GLS.VectorGeometry,
  24. GLS.OpenGLTokens,
  25. GLS.ApplicationFileIO,
  26. GLS.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. // ------------------------------------------------------------------
  68. implementation
  69. // ------------------------------------------------------------------
  70. const
  71. cDefaultLast = '}';
  72. var
  73. Win32PlatformIsUnicode : Boolean;
  74. // ------------------
  75. // ------------------ TGLWindowsBitmapFont ------------------
  76. // ------------------
  77. constructor TGLWindowsBitmapFont.Create(AOwner: TComponent);
  78. begin
  79. inherited;
  80. FFont := TFont.Create;
  81. FFont.Color := clWhite;
  82. FFont.OnChange := NotifyChange;
  83. GlyphsAlpha := tiaAlphaFromIntensity;
  84. EnsureChars(' ', cDefaultLast);
  85. end;
  86. destructor TGLWindowsBitmapFont.Destroy;
  87. begin
  88. FFont.Free;
  89. Ranges.Clear;
  90. inherited;
  91. end;
  92. function TGLWindowsBitmapFont.FontTextureWidth: Integer;
  93. begin
  94. Result := Glyphs.Width;
  95. end;
  96. function TGLWindowsBitmapFont.FontTextureHeight: Integer;
  97. begin
  98. Result := Glyphs.Height;
  99. end;
  100. procedure TGLWindowsBitmapFont.SetFont(value: TFont);
  101. begin
  102. FFont.Assign(value);
  103. end;
  104. procedure TGLWindowsBitmapFont.NotifyChange(Sender: TObject);
  105. begin
  106. StreamlineRanges;
  107. FreeTextureHandle;
  108. InvalidateUsers;
  109. inherited;
  110. end;
  111. procedure TGLWindowsBitmapFont.LoadWindowsFont;
  112. procedure ComputeCharRects(bitmap: TBitmap);
  113. var
  114. px, py, cw, n, x, y: Integer;
  115. PaddedHeight : integer;
  116. buffer : array[0..2] of WideChar;
  117. p : PCharInfo;
  118. r : TRect;
  119. begin
  120. buffer[1] := WideChar(#32);
  121. buffer[2] := WideChar(#0);
  122. PaddedHeight:= CharHeight + GlyphsIntervalY;
  123. x := bitmap.Width; y := bitmap.Height;
  124. px := 0;
  125. py := 0;
  126. if y < CharHeight then px := x;
  127. p := @FChars[0];
  128. for n := 0 to CharacterCount - 1 do
  129. begin
  130. cw := p.w;
  131. if cw > 0 then
  132. begin
  133. Inc(cw, GlyphsIntervalX);
  134. if px + cw > x then
  135. begin
  136. px := 0;
  137. Inc(py, PaddedHeight);
  138. if py + PaddedHeight > y then
  139. begin
  140. py := bitmap.Height;
  141. y := py + TextureHeight;
  142. bitmap.Height := y;
  143. with bitmap.Canvas do
  144. begin
  145. Brush.Style := bsSolid;
  146. Brush.Color := clBlack;
  147. FillRect(Rect(0, py, x, y));
  148. end;
  149. end;
  150. end;
  151. if Assigned(bitmap) then
  152. begin
  153. //+1 makes right align (padding left);
  154. // I prefer padding right for compatibility with bitmap font...
  155. p.l := px;
  156. //should make it consistent, same as above
  157. p.t := py;
  158. r.Left := px;
  159. r.Top := py;
  160. r.Right := px + cw;
  161. r.Bottom := py + PaddedHeight;
  162. buffer[0] := TileIndexToChar(n);
  163. // Draw the Char, the trailing space is to properly handle the italics.
  164. // credits to the Unicode version of SynEdit for this function call. GPL/MPL as GLScene
  165. ExtTextOutW(bitmap.Canvas.Handle, p.l, p.t, ETO_CLIPPED, @r, buffer, 1, nil);
  166. end;
  167. Inc(px, cw);
  168. end
  169. else
  170. begin
  171. p.l := 0;
  172. p.t := 0;
  173. end;
  174. inc(p);
  175. end;
  176. end;
  177. // credits to the Unicode version of SynEdit for this function. GPL/MPL as GLScene
  178. function GetTextSize(DC: HDC; Str: PWideChar; Count: Integer): TSize;
  179. var tm: TTextMetric;
  180. begin
  181. Result.cx := 0;
  182. Result.cy := 0;
  183. GetTextExtentPoint32W(DC, Str, Count, Result);
  184. if not Win32PlatformIsUnicode then
  185. begin
  186. GetTextMetricsW(DC, tm);
  187. if tm.tmPitchAndFamily and TMPF_TRUETYPE <> 0 then
  188. Result.cx := Result.cx - tm.tmOverhang
  189. else
  190. Result.cx := tm.tmAveCharWidth * Count;
  191. end;
  192. end;
  193. var
  194. bitmap: TBitmap;
  195. ch: widechar;
  196. i, cw, nbChars, n: Integer;
  197. begin
  198. InvalidateUsers;
  199. Glyphs.OnChange := nil;
  200. //accessing Bitmap might trigger onchange
  201. bitmap := Glyphs.Bitmap;
  202. bitmap.Height := 0;
  203. //due to lazarus doesn't properly support pixel formats
  204. bitmap.PixelFormat := pf32bit;
  205. with bitmap.Canvas do
  206. begin
  207. Font := Self.Font;
  208. Font.Color := clWhite;
  209. // get characters dimensions for the font
  210. // character size without padding; paddings are used from GlyphsInterval
  211. CharWidth := Round(MaxInteger(TextWidth('M'), TextWidth('W'), TextWidth('_')));
  212. CharHeight := TextHeight('"_pI|,');
  213. // used for padding
  214. GlyphsIntervalX := 1;
  215. GlyphsIntervalY := 1;
  216. if fsItalic in Font.Style then
  217. begin
  218. // italics aren't properly acknowledged in font width
  219. HSpaceFix := -(CharWidth div 3);
  220. CharWidth := CharWidth - HSpaceFix;
  221. end
  222. else
  223. HSpaceFix := 0;
  224. end;
  225. nbChars := CharacterCount;
  226. // Retrieve width of all characters (texture width)
  227. ResetCharWidths(0);
  228. n := 0;
  229. for i := 0 to nbChars - 1 do
  230. begin
  231. ch := TileIndexToChar(i);
  232. cw := GetTextSize(bitmap.canvas.Handle, @ch, 1).cx-HSpaceFix;
  233. n := n + cw + GlyphsIntervalX;
  234. SetCharWidths(i, cw);
  235. end;
  236. //try to make best guess...
  237. //~total pixels, including some waste (10%)
  238. n := n * (CharHeight + GlyphsIntervalY) * 11 div 10;
  239. TextureWidth := min(512, RoundUpToPowerOf2( round(sqrt(n)) ));
  240. TextureHeight := min(512, RoundUpToPowerOf2( n div TextureWidth));
  241. bitmap.Width := TextureWidth;
  242. ComputeCharRects(bitmap);
  243. FCharsLoaded := true;
  244. Glyphs.OnChange := OnGlyphsChanged;
  245. end;
  246. function TGLWindowsBitmapFont.StoreRanges: Boolean;
  247. begin
  248. Result := (Ranges.Count <> 1) or (Ranges[0].StartASCII[1] <> ' ') or (Ranges[0].StopASCII[1] <> cDefaultLast);
  249. end;
  250. type
  251. TFriendlyRange = class(TGLBitmapFontRange);
  252. procedure TGLWindowsBitmapFont.StreamlineRanges;
  253. var
  254. I, C: Integer;
  255. begin
  256. C := 0;
  257. for I := 0 to Ranges.Count - 1 do
  258. begin
  259. TFriendlyRange(Ranges[I]).FStartGlyphIdx := C;
  260. Inc(C, Ranges[I].CharCount);
  261. TFriendlyRange(Ranges[I]).FStopGlyphIdx := MaxInteger(C - 1, 0);
  262. end;
  263. end;
  264. procedure TGLWindowsBitmapFont.SetList(const AList: TGLIntegerList);
  265. var
  266. i : integer;
  267. f, n, s : integer;
  268. begin
  269. //add existing ranges
  270. for I := 0 to Ranges.Count - 1 do
  271. with Ranges.Items[I] do
  272. AList.AddSerie(integer(StartASCII[1]), 1, CharCount);
  273. AList.SortAndRemoveDuplicates;
  274. Ranges.Clear;
  275. Ranges.BeginUpdate;
  276. if AList.Count > 0 then
  277. begin
  278. i := 0;
  279. while (i < AList.Count) and (AList[i] < 32) do inc(i);
  280. while i < AList.Count do
  281. begin
  282. f := AList[i]; n := f; s := Ranges.CharacterCount;
  283. while (i < AList.Count) and (n = AList[i]) do
  284. begin
  285. inc(i);
  286. inc(n);
  287. end;
  288. Ranges.Add(widechar(f), widechar(pred(n))).StartGlyphIdx := s;
  289. end;
  290. end;
  291. Ranges.EndUpdate;
  292. TextureChanged;
  293. InvalidateUsers;
  294. end;
  295. //add characters to internal list
  296. procedure TGLWindowsBitmapFont.EnsureChars(const AStart, AEnd: widechar);
  297. var
  298. c : WideChar;
  299. ACharList : TGLIntegerList;
  300. begin
  301. ACharList := TGLIntegerList.Create;
  302. for c := AStart to AEnd do
  303. ACharList.Add(integer(c));
  304. SetList(ACharList);
  305. ACharList.Free;
  306. end;
  307. //add characters to internal list
  308. procedure TGLWindowsBitmapFont.EnsureString(const s: String);
  309. var
  310. i : Integer;
  311. ACharList : TGLIntegerList;
  312. begin
  313. ACharList := TGLIntegerList.Create;
  314. for i := 1 to length(s) do
  315. ACharList.Add(integer(s[i]));
  316. SetList(ACharList);
  317. ACharList.Free;
  318. end;
  319. procedure TGLWindowsBitmapFont.PrepareImage(var ARci: TGLRenderContextInfo);
  320. begin
  321. LoadWindowsFont;
  322. inherited PrepareImage(ARci);
  323. end;
  324. function TGLWindowsBitmapFont.TextureFormat: Integer;
  325. begin
  326. Result := GL_ALPHA;
  327. end;
  328. // ------------------------------------------------------------------
  329. initialization
  330. // ------------------------------------------------------------------
  331. Win32PlatformIsUnicode := (Win32Platform = VER_PLATFORM_WIN32_NT);
  332. RegisterClasses([TGLWindowsBitmapFont]);
  333. end.