1 /* -*- coding: utf-8 -*- */
2 /*
3  * font-pangoft2-tex.c:
4  * Simple example for text texture rendering with PangoFT2.
5  *
6  * written by Naofumi Yasufuku  <naofumi@users.sourceforge.net>
7  */
8 
9 #include <stdlib.h>
10 #include <string.h>
11 #include <math.h>
12 
13 #include <gtk/gtk.h>
14 #include <pango/pangoft2.h>
15 
16 #include <gtk/gtkgl.h>
17 
18 #ifdef G_OS_WIN32
19 #define WIN32_LEAN_AND_MEAN 1
20 #include <windows.h>
21 #endif
22 
23 #include <GL/gl.h>
24 #include <GL/glu.h>
25 
26 #define TIMEOUT_INTERVAL 10
27 
28 #define FOVY_2 20.0
29 #define Z_NEAR 3.0
30 
31 static const char *text = "This text is rendered with Παν語FT2.";
32 
33 static PangoContext *ft2_context = NULL;
34 
35 typedef struct _TextTexture {
36   GLuint  name;
37   GLsizei size;
38   GLvoid *texels;
39 } TextTexture;
40 
41 static TextTexture text_texture = {
42   0, 0, NULL
43 };
44 
45 static gboolean
gl_tex_create_texture(GLuint * texture)46 gl_tex_create_texture (GLuint *texture)
47 {
48   GLuint tex_name;
49   GLint size;
50   GLvoid *texels;
51 
52   glGetIntegerv (GL_MAX_TEXTURE_SIZE, &size);
53 
54   do
55     {
56       GLint width;
57 
58       glTexImage2D (GL_PROXY_TEXTURE_2D, 0, GL_RGBA,
59                     size, size, 0,
60                     GL_RGBA, GL_UNSIGNED_BYTE, NULL);
61 
62       glGetTexLevelParameteriv (GL_PROXY_TEXTURE_2D, 0,
63                                 GL_TEXTURE_WIDTH, &width);
64       if (width != 0)
65         break;
66 
67       size >>= 1;
68     }
69   while (size > 0);
70 
71   if (size == 0)
72     {
73       g_print ("There are not enough resources for the texture.\n");
74       return FALSE;
75     }
76 
77   texels = g_malloc (size * size * 4);
78   memset (texels, 0, size * size * 4);
79 
80   glPixelStorei (GL_UNPACK_ALIGNMENT, 4);
81 
82   glGenTextures (1, &tex_name);
83 
84   glBindTexture (GL_TEXTURE_2D, tex_name);
85   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
86   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
87   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
88   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
89   glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA,
90                 size, size, 0,
91                 GL_RGBA, GL_UNSIGNED_BYTE,
92                 texels);
93 
94   text_texture.name = tex_name;
95   text_texture.size = size;
96   text_texture.texels = texels;
97 
98   *texture = text_texture.name;
99 
100   return TRUE;
101 }
102 
103 static void
gl_tex_delete_texture(void)104 gl_tex_delete_texture (void)
105 {
106   glDeleteTextures (1, &text_texture.name);
107   g_free (text_texture.texels);
108 }
109 
110 static gboolean
gl_tex_pango_ft2_render_layout(PangoLayout * layout,GLuint * texture,GLfloat * s0,GLfloat * s1,GLfloat * t0,GLfloat * t1)111 gl_tex_pango_ft2_render_layout (PangoLayout *layout,
112                                 GLuint *texture,
113                                 GLfloat *s0,
114                                 GLfloat *s1,
115                                 GLfloat *t0,
116                                 GLfloat *t1)
117 {
118   PangoRectangle logical_rect;
119   FT_Bitmap bitmap;
120   guint32 *t;
121   GLfloat color[4];
122   guint32 rgb;
123   GLfloat a;
124   guint8 *row, *row_end;
125   int i;
126 
127   if (text_texture.size == 0)
128     return FALSE;
129 
130   pango_layout_get_extents (layout, NULL, &logical_rect);
131   if (logical_rect.width == 0 || logical_rect.height == 0)
132     return FALSE;
133 
134   bitmap.rows = PANGO_PIXELS (logical_rect.height);
135   bitmap.width = PANGO_PIXELS (logical_rect.width);
136 
137   /* Ad hoc :-p */
138   if (bitmap.width > text_texture.size || bitmap.rows > text_texture.size)
139     return FALSE;
140 
141   bitmap.pitch = bitmap.width;
142   bitmap.buffer = g_malloc (bitmap.rows * bitmap.width);
143   bitmap.num_grays = 256;
144   bitmap.pixel_mode = ft_pixel_mode_grays;
145 
146   memset (bitmap.buffer, 0, bitmap.rows * bitmap.width);
147   pango_ft2_render_layout (&bitmap, layout,
148                            PANGO_PIXELS (-logical_rect.x), 0);
149 
150   glGetFloatv (GL_CURRENT_COLOR, color);
151 #if !defined(GL_VERSION_1_2) && G_BYTE_ORDER == G_LITTLE_ENDIAN
152   rgb =  ((guint32) (color[0] * 255.0))        |
153         (((guint32) (color[1] * 255.0)) << 8)  |
154         (((guint32) (color[2] * 255.0)) << 16);
155 #else
156   rgb = (((guint32) (color[0] * 255.0)) << 24) |
157         (((guint32) (color[1] * 255.0)) << 16) |
158         (((guint32) (color[2] * 255.0)) << 8);
159 #endif
160   a = color[3];
161 
162   row = bitmap.buffer + bitmap.rows * bitmap.width; /* past-the-end */
163   row_end = bitmap.buffer;      /* beginning */
164 
165   t = (guint32 *) text_texture.texels;
166 
167   if (a == 1.0)
168     {
169       do
170         {
171           row -= bitmap.width;
172           for (i = 0; i < bitmap.width; i++)
173 #if !defined(GL_VERSION_1_2) && G_BYTE_ORDER == G_LITTLE_ENDIAN
174             *t++ = rgb | (((guint32) row[i]) << 24);
175 #else
176             *t++ = rgb | ((guint32) row[i]);
177 #endif
178         }
179       while (row != row_end);
180     }
181   else
182     {
183       do
184         {
185           row -= bitmap.width;
186           for (i = 0; i < bitmap.width; i++)
187 #if !defined(GL_VERSION_1_2) && G_BYTE_ORDER == G_LITTLE_ENDIAN
188             *t++ = rgb | (((guint32) (a * row[i])) << 24);
189 #else
190             *t++ = rgb | ((guint32) (a * row[i]));
191 #endif
192         }
193       while (row != row_end);
194     }
195 
196   glPixelStorei (GL_UNPACK_ALIGNMENT, 4);
197 
198   glBindTexture (GL_TEXTURE_2D, text_texture.name);
199 #if !defined(GL_VERSION_1_2)
200   glTexSubImage2D (GL_TEXTURE_2D, 0,
201                    0, 0, bitmap.width, bitmap.rows,
202                    GL_RGBA, GL_UNSIGNED_BYTE,
203                    text_texture.texels);
204 #else
205   glTexSubImage2D (GL_TEXTURE_2D, 0,
206                    0, 0, bitmap.width, bitmap.rows,
207                    GL_RGBA, GL_UNSIGNED_INT_8_8_8_8,
208                    text_texture.texels);
209 #endif
210 
211   *texture = text_texture.name;
212 
213   *s0 = 0.0;
214   *s1 = (GLfloat) bitmap.width / (GLfloat) text_texture.size;
215 
216   *t0 = 0.0;
217   *t1 = (GLfloat) bitmap.rows / (GLfloat) text_texture.size;
218 
219   g_free (bitmap.buffer);
220 
221   return TRUE;
222 }
223 
224 static void
realize(GtkWidget * widget,gpointer data)225 realize (GtkWidget *widget,
226          gpointer   data)
227 {
228   GdkGLContext *glcontext = gtk_widget_get_gl_context (widget);
229   GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (widget);
230 
231   GLUquadricObj *qobj;
232   static GLfloat light_diffuse[] = {1.0, 0.0, 0.0, 1.0};
233   static GLfloat light_position[] = {1.0, 1.0, 1.0, 0.0};
234 
235   GLuint texture;
236 
237   /*** OpenGL BEGIN ***/
238   if (!gdk_gl_drawable_gl_begin (gldrawable, glcontext))
239     return;
240 
241   qobj = gluNewQuadric ();
242   gluQuadricDrawStyle (qobj, GLU_FILL);
243   glNewList (1, GL_COMPILE);
244   gluSphere (qobj, 1.0, 20, 20);
245   glEndList ();
246 
247   glLightfv (GL_LIGHT0, GL_DIFFUSE, light_diffuse);
248   glLightfv (GL_LIGHT0, GL_POSITION, light_position);
249   glEnable (GL_LIGHTING);
250   glEnable (GL_LIGHT0);
251   glEnable (GL_DEPTH_TEST);
252 
253   glClearColor (0.0, 0.0, 0.0, 0.0);
254   glClearDepth (1.0);
255 
256   /* Create texture. */
257   gl_tex_create_texture (&texture);
258 
259   gdk_gl_drawable_gl_end (gldrawable);
260   /*** OpenGL END ***/
261 
262   /* Get PangoFT2 context. */
263   ft2_context = pango_ft2_get_context (72, 72);
264 }
265 
266 static gboolean
configure_event(GtkWidget * widget,GdkEventConfigure * event,gpointer data)267 configure_event (GtkWidget         *widget,
268                  GdkEventConfigure *event,
269                  gpointer           data)
270 {
271   GdkGLContext *glcontext = gtk_widget_get_gl_context (widget);
272   GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (widget);
273 
274   GLsizei w = widget->allocation.width;
275   GLsizei h = widget->allocation.height;
276 
277   /*** OpenGL BEGIN ***/
278   if (!gdk_gl_drawable_gl_begin (gldrawable, glcontext))
279     return FALSE;
280 
281   glViewport (0, 0, w, h);
282 
283   glMatrixMode (GL_PROJECTION);
284   glLoadIdentity ();
285   gluPerspective (2.0 * FOVY_2,
286                   (GLfloat) w / (GLfloat) h,
287                   Z_NEAR,
288                   2.5 * Z_NEAR);
289 
290   glMatrixMode (GL_MODELVIEW);
291   glLoadIdentity ();
292   gluLookAt (0.0, 0.0, Z_NEAR,
293              0.0, 0.0, 0.0,
294              0.0, 1.0, 0.0);
295   glTranslatef (0.0, 0.0, -Z_NEAR);
296 
297   gdk_gl_drawable_gl_end (gldrawable);
298   /*** OpenGL END ***/
299 
300   return TRUE;
301 }
302 
303 #define ANGLE   30.0
304 /* tan (ANGLE * PI / 180.0) */
305 #define TANGENT 0.57735
306 
307 #define TEXT_Z_NEAR  2.0
308 #define TEXT_Z_FAR  -5.0
309 #define TEXT_Z_DIFF  0.005
310 
311 static GLfloat text_z = TEXT_Z_NEAR;
312 
313 static gboolean animate = TRUE;
314 
315 static gboolean
expose_event(GtkWidget * widget,GdkEventExpose * event,gpointer data)316 expose_event (GtkWidget      *widget,
317               GdkEventExpose *event,
318               gpointer        data)
319 {
320   GdkGLContext *glcontext = gtk_widget_get_gl_context (widget);
321   GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (widget);
322 
323   PangoContext *widget_context;
324   PangoFontDescription *font_desc;
325   PangoLayout *layout;
326 
327   GLuint texture;
328   GLfloat s0, s1, t0, t1;
329   gboolean ret;
330 
331   /* Font */
332   widget_context = gtk_widget_get_pango_context (widget);
333   font_desc = pango_context_get_font_description (widget_context);
334   pango_font_description_set_size (font_desc, 24 * PANGO_SCALE);
335   pango_context_set_font_description (ft2_context, font_desc);
336 
337   /* Text layout */
338   layout = pango_layout_new (ft2_context);
339   pango_layout_set_width (layout, PANGO_SCALE * 200);
340   pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
341   pango_layout_set_text (layout, text, -1);
342 
343   /*** OpenGL BEGIN ***/
344   if (!gdk_gl_drawable_gl_begin (gldrawable, glcontext))
345     return FALSE;
346 
347   glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
348 
349   glCallList (1);
350 
351   /* Text color */
352   glColor3f (1.0, 0.9, 0.0);
353 
354   /* Render text */
355   ret = gl_tex_pango_ft2_render_layout (layout,
356                                         &texture,
357                                         &s0, &s1, &t0, &t1);
358   if (ret)
359     {
360       glPushMatrix ();
361         glTranslatef (0.0, -text_z * TANGENT, text_z + 2.0);
362         glRotatef (ANGLE, 1.0, 0.0, 0.0);
363 
364         glEnable (GL_TEXTURE_2D);
365         glEnable (GL_BLEND);
366         glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
367 
368         glBindTexture (GL_TEXTURE_2D, texture);
369         glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
370         glBegin (GL_QUADS);
371           glTexCoord2f (s0, t0); glVertex3f (-1.0, 0.0,  1.0);
372           glTexCoord2f (s0, t1); glVertex3f (-1.0, 0.0, -1.0);
373           glTexCoord2f (s1, t1); glVertex3f ( 1.0, 0.0, -1.0);
374           glTexCoord2f (s1, t0); glVertex3f ( 1.0, 0.0,  1.0);
375         glEnd ();
376 
377         glDisable (GL_BLEND);
378         glDisable (GL_TEXTURE_2D);
379 
380       glPopMatrix ();
381     }
382 
383   if (gdk_gl_drawable_is_double_buffered (gldrawable))
384     gdk_gl_drawable_swap_buffers (gldrawable);
385   else
386     glFlush ();
387 
388   gdk_gl_drawable_gl_end (gldrawable);
389   /*** OpenGL END ***/
390 
391   g_object_unref (G_OBJECT (layout));
392 
393   return TRUE;
394 }
395 
396 static void
unrealize(GtkWidget * widget,gpointer data)397 unrealize (GtkWidget *widget,
398 	   gpointer   data)
399 {
400   GdkGLContext *glcontext = gtk_widget_get_gl_context (widget);
401   GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (widget);
402 
403   /*** OpenGL BEGIN ***/
404   if (!gdk_gl_drawable_gl_begin (gldrawable, glcontext))
405     return;
406 
407   gl_tex_delete_texture ();
408 
409   gdk_gl_drawable_gl_end (gldrawable);
410   /*** OpenGL END ***/
411 
412   g_object_unref (G_OBJECT (ft2_context));
413   pango_ft2_shutdown_display ();
414 }
415 
416 static gboolean
timeout(GtkWidget * widget)417 timeout (GtkWidget *widget)
418 {
419   text_z -= TEXT_Z_DIFF;
420   if (text_z <= TEXT_Z_FAR)
421     text_z = TEXT_Z_NEAR;
422 
423   /* Invalidate the whole window. */
424   gdk_window_invalidate_rect (widget->window, &widget->allocation, FALSE);
425 
426   /* Update synchronously. */
427   gdk_window_process_updates (widget->window, FALSE);
428 
429   return TRUE;
430 }
431 
432 static guint timeout_id = 0;
433 
434 static void
timeout_add(GtkWidget * widget)435 timeout_add (GtkWidget *widget)
436 {
437   if (timeout_id == 0)
438     {
439       timeout_id = g_timeout_add (TIMEOUT_INTERVAL,
440                                   (GSourceFunc) timeout,
441                                   widget);
442     }
443 }
444 
445 static void
timeout_remove(GtkWidget * widget)446 timeout_remove (GtkWidget *widget)
447 {
448   if (timeout_id != 0)
449     {
450       g_source_remove (timeout_id);
451       timeout_id = 0;
452     }
453 }
454 
455 static gboolean
map_event(GtkWidget * widget,GdkEvent * event,gpointer data)456 map_event (GtkWidget *widget,
457 	   GdkEvent  *event,
458 	   gpointer   data)
459 {
460   if (animate)
461     timeout_add (widget);
462 
463   return TRUE;
464 }
465 
466 static gboolean
unmap_event(GtkWidget * widget,GdkEvent * event,gpointer data)467 unmap_event (GtkWidget *widget,
468 	     GdkEvent  *event,
469 	     gpointer   data)
470 {
471   timeout_remove (widget);
472 
473   return TRUE;
474 }
475 
476 static gboolean
visibility_notify_event(GtkWidget * widget,GdkEventVisibility * event,gpointer data)477 visibility_notify_event (GtkWidget          *widget,
478 			 GdkEventVisibility *event,
479 			 gpointer            data)
480 {
481   if (animate)
482     {
483       if (event->state == GDK_VISIBILITY_FULLY_OBSCURED)
484 	timeout_remove (widget);
485       else
486 	timeout_add (widget);
487     }
488 
489   return TRUE;
490 }
491 
492 int
main(int argc,char * argv[])493 main (int   argc,
494       char *argv[])
495 {
496   GdkGLConfig *glconfig;
497 
498   GtkWidget *window;
499   GtkWidget *vbox;
500   GtkWidget *drawing_area;
501   GtkWidget *button;
502 
503   /*
504    * Init GTK.
505    */
506 
507   gtk_init (&argc, &argv);
508 
509   /*
510    * Init GtkGLExt.
511    */
512 
513   gtk_gl_init (&argc, &argv);
514 
515   /*
516    * Configure OpenGL-capable visual.
517    */
518 
519   /* Try double-buffered visual */
520   glconfig = gdk_gl_config_new_by_mode (GDK_GL_MODE_RGB    |
521                                         GDK_GL_MODE_DEPTH  |
522                                         GDK_GL_MODE_DOUBLE);
523   if (glconfig == NULL)
524     {
525       g_print ("*** Cannot find the double-buffered visual.\n");
526       g_print ("*** Trying single-buffered visual.\n");
527 
528       /* Try single-buffered visual */
529       glconfig = gdk_gl_config_new_by_mode (GDK_GL_MODE_RGB   |
530                                             GDK_GL_MODE_DEPTH);
531       if (glconfig == NULL)
532         {
533           g_print ("*** No appropriate OpenGL-capable visual found.\n");
534           exit (1);
535         }
536     }
537 
538   /*
539    * Top-level window.
540    */
541 
542   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
543   gtk_window_set_title (GTK_WINDOW (window), "font-pangoft2");
544 
545   /* Get automatically redrawn if any of their children changed allocation. */
546   gtk_container_set_reallocate_redraws (GTK_CONTAINER (window), TRUE);
547 
548   g_signal_connect (G_OBJECT (window), "delete_event",
549                     G_CALLBACK (gtk_main_quit), NULL);
550 
551   /*
552    * VBox.
553    */
554 
555   vbox = gtk_vbox_new (FALSE, 0);
556   gtk_container_add (GTK_CONTAINER (window), vbox);
557   gtk_widget_show (vbox);
558 
559   /*
560    * Drawing area for drawing OpenGL scene.
561    */
562 
563   drawing_area = gtk_drawing_area_new ();
564   gtk_widget_set_size_request (drawing_area, 200, 200);
565 
566   /* Set OpenGL-capability to the widget. */
567   gtk_widget_set_gl_capability (drawing_area,
568                                 glconfig,
569                                 NULL,
570                                 TRUE,
571                                 GDK_GL_RGBA_TYPE);
572 
573   g_signal_connect_after (G_OBJECT (drawing_area), "realize",
574                           G_CALLBACK (realize), NULL);
575   g_signal_connect (G_OBJECT (drawing_area), "configure_event",
576 		    G_CALLBACK (configure_event), NULL);
577   g_signal_connect (G_OBJECT (drawing_area), "expose_event",
578 		    G_CALLBACK (expose_event), NULL);
579   g_signal_connect (G_OBJECT (drawing_area), "unrealize",
580 		    G_CALLBACK (unrealize), NULL);
581 
582   g_signal_connect (G_OBJECT (drawing_area), "map_event",
583 		    G_CALLBACK (map_event), NULL);
584   g_signal_connect (G_OBJECT (drawing_area), "unmap_event",
585 		    G_CALLBACK (unmap_event), NULL);
586   g_signal_connect (G_OBJECT (drawing_area), "visibility_notify_event",
587 		    G_CALLBACK (visibility_notify_event), NULL);
588 
589   gtk_box_pack_start (GTK_BOX (vbox), drawing_area, TRUE, TRUE, 0);
590 
591   gtk_widget_show (drawing_area);
592 
593   /*
594    * Simple quit button.
595    */
596 
597   button = gtk_button_new_with_label ("Quit");
598 
599   g_signal_connect (G_OBJECT (button), "clicked",
600                     G_CALLBACK (gtk_main_quit), NULL);
601 
602   gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
603 
604   gtk_widget_show (button);
605 
606   /*
607    * Show window.
608    */
609 
610   gtk_widget_show (window);
611 
612   /*
613    * Main loop.
614    */
615 
616   gtk_main ();
617 
618   return 0;
619 }
620