1 unit TAOpenGL;
2 
3 {$mode objfpc}{$H+}
4 
5 interface
6 
7 uses
8   Classes, LazFileUtils, FPCanvas, FPImage, gl,
9   EasyLazFreeType, LazFreeTypeFPImageDrawer, LazFreeTypeFontCollection, TAFonts;
10 
11 type
12   TTextureCacheItem = class
13     TextureID: Gluint;
14     TextWidth: Integer;
15     TextHeight: Integer;
16   end;
17 
18   TGLFreeTypeHelper = class
19   private
20     FFont: TFreeTypeFont;
21     FImg: TFPMemoryImage;
22     FDrawer: TFPImageFreeTypeDrawer;
23     FTextureCache: TStringList;
24   protected
BuildTextureNamenull25     function BuildTextureName(AText: String): String;
26     procedure CreateTexture(AText: String; out ATextWidth, ATextHeight,
27       ATextureWidth, ATextureHeight: Integer; out ATextureID: GLuint);
FindTexturenull28     function FindTexture(AText: String; out ATextWidth, ATextHeight,
29       ATextureWidth, ATextureHeight: Integer): GLuint;
30   public
31     constructor Create;
32     destructor Destroy; override;
33     procedure RenderText(AText: String; Alignments: TFreeTypeAlignments);
34     procedure SetFont(AFontName: String; AFontSize: Integer;
35       ABold: Boolean = false; AItalic: Boolean = false;
36       AUnderline: Boolean = false; AStrikethrough: Boolean = false);
37     procedure TextExtent(AText: String; out AWidth, AHeight: Integer);
38   end;
39 
40 var
41   GLFreeTypeHelper: TGLFreeTypeHelper = nil;
42 
43 
44 implementation
45 
46 uses
47   SysUtils;
48 
NextPowerOf2null49 function NextPowerOf2(n: Integer): Integer;
50 begin
51   Result := 1;
52   while Result < n do
53     Result := Result * 2;
54 end;
55 
56 
57 { TGLFreeTypeHelper }
58 
59 constructor TGLFreeTypeHelper.Create;
60 begin
61   FImg := TFPMemoryImage.Create(8, 8);  // dummy size, will be updated when needed
62   FDrawer := TFPImageFreeTypeDrawer.Create(FImg);
63   FTextureCache := TStringList.Create;
64   FTextureCache.Sorted := true;
65 end;
66 
67 destructor TGLFreeTypeHelper.Destroy;
68 var
69   i: Integer;
70   item: TTextureCacheItem;
71 begin
72   for i:=0 to FTextureCache.Count-1 do begin
73     item := TTextureCacheItem(FTextureCache.Objects[i]);
74     glDeleteTextures(1, @item.TextureID);
75     item.Free;
76   end;
77   FTextureCache.Free;
78   if FFont <> nil then FFont.Free;
79   FDrawer.Free;
80   FImg.Free;
81   inherited;
82 end;
83 
84 { The texture items are stored in the FTextureCache list and can be identified
85   by means of their name which is composed of the text and font parameters.
86   The name of the texture items is calculated here. }
TGLFreeTypeHelper.BuildTextureNamenull87 function TGLFreeTypeHelper.BuildTextureName(AText: String): String;
88 begin
89   Result := Format('%s|%s|%d|%s', [
90     AText, FFont.Family, round(FFont.SizeInPoints*100), FFont.StyleAsString
91   ]);
92 end;
93 
94 procedure TGLFreeTypeHelper.CreateTexture(AText: String; out ATextWidth, ATextHeight,
95   ATextureWidth, ATextureHeight: Integer; out ATextureID: GLuint);
96 var
97   expanded_data: packed array of byte = nil;
98   i, j: Integer;
99   c: TFPColor;
100 begin
101   if FFont = nil then
102     raise Exception.Create('No font selected.');
103 
104   ATextWidth := round(FFont.TextWidth(AText));
105   ATextHeight := round(FFont.TextHeight(AText));
106   ATextureWidth := NextPowerOf2(ATextWidth);
107   ATextureHeight := NextPowerOf2(ATextHeight);
108 
109   FImg.SetSize(ATextureWidth, ATextureHeight);
110   FDrawer.FillPixels(colTransparent);
111   FDrawer.DrawText(AText, FFont, 0,0, colRed, [ftaLeft, ftaTop]);
112 
113   SetLength(expanded_data, 2*ATextureWidth * ATextureHeight);
114   for j:=0 to ATextureHeight-1 do
115     for i:=0 to ATextureWidth-1 do
116     begin
117       expanded_data[2*(i + j*ATextureWidth)] := 255;     // Luminosity
118       if (i > ATextWidth) or (j > ATextHeight) then
119         expanded_data[2*(i + j*ATextureWidth) + 1] := 0  // Alpha
120       else begin
121         c := FImg.Colors[i,j];
122         expanded_data[2*(i + j*ATextureWidth) + 1] := c.Alpha shr 8;
123       end;
124     end;
125 
126   // Set up texture parameters
127   glGenTextures(1, @ATextureID);
128   glBindTexture(GL_TEXTURE_2D, ATextureID);
129 
130   // Create the texture
131   // Note that we are using GL_LUMINANCE_ALPHA to indicate that we are using
132   // two-channel data
133   glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, ATextureWidth, ATextureHeight, 0,
134     GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, @expanded_data[0]);
135 
136   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
137   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
138 end;
139 
140 { Finds the texture id for the given text. Returns the texture id and the
141   size of text and texture. Note that the texture size must be a power of 2 and
142   thus can be different from the text size. }
TGLFreeTypeHelper.FindTexturenull143 function TGLFreeTypeHelper.FindTexture(AText: String;
144   out ATextWidth, ATextHeight, ATextureWidth, ATextureHeight: Integer): GLuint;
145 var
146   idx: Integer;
147   item: TTextureCacheItem;
148   txname: String;
149 begin
150   txname := BuildTextureName(AText);
151   idx := FTextureCache.IndexOf(txname);
152   if idx = -1 then begin
153     CreateTexture(AText, ATextWidth, ATextHeight, ATextureWidth, ATextureHeight, Result);
154     item := TTextureCacheItem.Create;
155     item.TextureID := Result;
156     item.TextWidth := ATextWidth;
157     item.TextHeight := ATextHeight;
158     FTextureCache.AddObject(txname, item);
159   end else begin
160     item := TTextureCacheItem(FTextureCache.Objects[idx]);
161     result := item.TextureID;
162     ATextWidth := item.TextWidth;
163     ATextHeight := item.TextHeight;
164     ATextureWidth := NextPowerOf2(ATextWidth);
165     ATextureHeight := NextPowerOf2(ATextHeight);
166   end;
167 end;
168 
169 procedure TGLFreeTypeHelper.RenderText(AText: String; Alignments: TFreeTypeAlignments);
170 var
171   textureID: GLuint;
172   w, h: Integer;
173   w2, h2: Integer;
174   sx, sy: Double;
175   dx, dy: Integer;
176 begin
177   textureID := FindTexture(AText, w, h, w2, h2);
178   sx := w / w2;
179   sy := h / h2;
180 
181   glMatrixMode(GL_MODELVIEW);
182   glPushMatrix;
183     // Note: We don't support ftaJustify)
184     if (ftaCenter in Alignments) then dx := -w div 2
185     else if (ftaRight in ALignments) then dx := -w
186     else dx := 0;
187 
188     if (ftaVerticalCenter in Alignments) then dy := -h div 2
189     else if (ftaBottom in Alignments) then dy := -h
190     else if (ftaBaseline in Alignments) then dy := - h + round(FFont.Descent)
191     else dy := 0;
192 
193     glTranslatef(dx, dy, 0);
194     glEnable(GL_TEXTURE_2D);
195       glBindTexture(GL_TEXTURE_2D, textureID);
196       glBegin(GL_QUADS);
197         glTexCoord2f(0.0, sy);  glVertex2f(0, h);
198         glTexCoord2f(sx, sy);   glVertex2f(w, h);
199         glTexCoord2f(sx, 0.0);  glVertex2f(w, 0);
200         glTexCoord2f(0.0, 0.0); glVertex2f(0, 0);
201       glEnd();
202     glDisable(GL_TEXTURE_2D);
203   glPopMatrix;
204 end;
205 
206 procedure TGLFreeTypeHelper.SetFont(AFontName: String; AFontSize: Integer;
207   ABold: Boolean = false; AItalic: Boolean = false;
208   AUnderline: Boolean = false; AStrikethrough: Boolean = false);
209 var
210   style: TFreeTypeStyles;
211 begin
212   if GLFreeTypeHelper = nil then
213     raise Exception.Create('InitFonts has not been called.');
214 
215   style := [];
216   if ABold then Include(style, ftsBold);
217   if AItalic then Include(style, ftsItalic);
218 
219   // Create a new font if not yet loaded
220   if (FFont = nil) or (FFont.Family <> AFontName) or (FFont.Style <> style) then
221   begin
222     FreeAndNil(FFont);
223     FFont := LoadFont(AFontName, style);
224     if FFont = nil then
225       raise Exception.CreateFmt('Font "%s" not found.', [AFontName]);
226   end;
227 
228   // Set the requested font attributes.
229   FFont.SizeInPoints := AFontSize;
230   FFont.UnderlineDecoration := AUnderline;
231   FFont.StrikeoutDecoration := AStrikethrough;
232   FFont.Hinted := true;
233   FFont.Quality := grqHighQuality;
234   //FFont.ClearType := true;
235 end;
236 
237 { Returns the width and height of the specified text. If the text already has
238   been handled with the same font parameters it is stored in the FTextureCache
239   list. If not, the size is determined from the font. }
240 procedure TGLFreeTypeHelper.TextExtent(AText: String; out AWidth, AHeight: Integer);
241 var
242   txname: String;
243   idx: Integer;
244   item: TTextureCacheItem;
245   textureID: Gluint;
246   w2, h2: Integer;
247 begin
248   txname := BuildTextureName(AText);
249   idx := FTextureCache.IndexOf(txname);
250   if idx = -1 then begin
251     CreateTexture(AText, AWidth, AHeight, w2, h2, textureID);
252     item := TTextureCacheItem.Create;
253     item.TextureID := textureID;
254     item.TextWidth := AWidth;
255     item.TextHeight := AHeight;
256     idx := FTextureCache.AddObject(txname, item);
257   end;
258 
259   item := TTextureCacheItem(FTextureCache.Objects[idx]);
260   AWidth := item.TextWidth;
261   AHeight := item.TextHeight;
262 end;
263 
264 end.
265 
266