/* -*- coding: utf-8 -*- */ /* * font-pangoft2-tex.c: * Simple example for text texture rendering with PangoFT2. * * written by Naofumi Yasufuku */ #include #include #include #include #include #include #ifdef G_OS_WIN32 #define WIN32_LEAN_AND_MEAN 1 #include #endif #include #include #define TIMEOUT_INTERVAL 10 #define FOVY_2 20.0 #define Z_NEAR 3.0 static const char *text = "This text is rendered with Παν語FT2."; static PangoContext *ft2_context = NULL; typedef struct _TextTexture { GLuint name; GLsizei size; GLvoid *texels; } TextTexture; static TextTexture text_texture = { 0, 0, NULL }; static gboolean gl_tex_create_texture (GLuint *texture) { GLuint tex_name; GLint size; GLvoid *texels; glGetIntegerv (GL_MAX_TEXTURE_SIZE, &size); do { GLint width; glTexImage2D (GL_PROXY_TEXTURE_2D, 0, GL_RGBA, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); glGetTexLevelParameteriv (GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width); if (width != 0) break; size >>= 1; } while (size > 0); if (size == 0) { g_print ("There are not enough resources for the texture.\n"); return FALSE; } texels = g_malloc (size * size * 4); memset (texels, 0, size * size * 4); glPixelStorei (GL_UNPACK_ALIGNMENT, 4); glGenTextures (1, &tex_name); glBindTexture (GL_TEXTURE_2D, tex_name); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, texels); text_texture.name = tex_name; text_texture.size = size; text_texture.texels = texels; *texture = text_texture.name; return TRUE; } static void gl_tex_delete_texture (void) { glDeleteTextures (1, &text_texture.name); g_free (text_texture.texels); } static gboolean gl_tex_pango_ft2_render_layout (PangoLayout *layout, GLuint *texture, GLfloat *s0, GLfloat *s1, GLfloat *t0, GLfloat *t1) { PangoRectangle logical_rect; FT_Bitmap bitmap; guint32 *t; GLfloat color[4]; guint32 rgb; GLfloat a; guint8 *row, *row_end; int i; if (text_texture.size == 0) return FALSE; pango_layout_get_extents (layout, NULL, &logical_rect); if (logical_rect.width == 0 || logical_rect.height == 0) return FALSE; bitmap.rows = PANGO_PIXELS (logical_rect.height); bitmap.width = PANGO_PIXELS (logical_rect.width); /* Ad hoc :-p */ if (bitmap.width > text_texture.size || bitmap.rows > text_texture.size) return FALSE; bitmap.pitch = bitmap.width; bitmap.buffer = g_malloc (bitmap.rows * bitmap.width); bitmap.num_grays = 256; bitmap.pixel_mode = ft_pixel_mode_grays; memset (bitmap.buffer, 0, bitmap.rows * bitmap.width); pango_ft2_render_layout (&bitmap, layout, PANGO_PIXELS (-logical_rect.x), 0); glGetFloatv (GL_CURRENT_COLOR, color); #if !defined(GL_VERSION_1_2) && G_BYTE_ORDER == G_LITTLE_ENDIAN rgb = ((guint32) (color[0] * 255.0)) | (((guint32) (color[1] * 255.0)) << 8) | (((guint32) (color[2] * 255.0)) << 16); #else rgb = (((guint32) (color[0] * 255.0)) << 24) | (((guint32) (color[1] * 255.0)) << 16) | (((guint32) (color[2] * 255.0)) << 8); #endif a = color[3]; row = bitmap.buffer + bitmap.rows * bitmap.width; /* past-the-end */ row_end = bitmap.buffer; /* beginning */ t = (guint32 *) text_texture.texels; if (a == 1.0) { do { row -= bitmap.width; for (i = 0; i < bitmap.width; i++) #if !defined(GL_VERSION_1_2) && G_BYTE_ORDER == G_LITTLE_ENDIAN *t++ = rgb | (((guint32) row[i]) << 24); #else *t++ = rgb | ((guint32) row[i]); #endif } while (row != row_end); } else { do { row -= bitmap.width; for (i = 0; i < bitmap.width; i++) #if !defined(GL_VERSION_1_2) && G_BYTE_ORDER == G_LITTLE_ENDIAN *t++ = rgb | (((guint32) (a * row[i])) << 24); #else *t++ = rgb | ((guint32) (a * row[i])); #endif } while (row != row_end); } glPixelStorei (GL_UNPACK_ALIGNMENT, 4); glBindTexture (GL_TEXTURE_2D, text_texture.name); #if !defined(GL_VERSION_1_2) glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, bitmap.width, bitmap.rows, GL_RGBA, GL_UNSIGNED_BYTE, text_texture.texels); #else glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, bitmap.width, bitmap.rows, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, text_texture.texels); #endif *texture = text_texture.name; *s0 = 0.0; *s1 = (GLfloat) bitmap.width / (GLfloat) text_texture.size; *t0 = 0.0; *t1 = (GLfloat) bitmap.rows / (GLfloat) text_texture.size; g_free (bitmap.buffer); return TRUE; } static void realize (GtkWidget *widget, gpointer data) { GdkGLContext *glcontext = gtk_widget_get_gl_context (widget); GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (widget); GLUquadricObj *qobj; static GLfloat light_diffuse[] = {1.0, 0.0, 0.0, 1.0}; static GLfloat light_position[] = {1.0, 1.0, 1.0, 0.0}; GLuint texture; /*** OpenGL BEGIN ***/ if (!gdk_gl_drawable_gl_begin (gldrawable, glcontext)) return; qobj = gluNewQuadric (); gluQuadricDrawStyle (qobj, GLU_FILL); glNewList (1, GL_COMPILE); gluSphere (qobj, 1.0, 20, 20); glEndList (); glLightfv (GL_LIGHT0, GL_DIFFUSE, light_diffuse); glLightfv (GL_LIGHT0, GL_POSITION, light_position); glEnable (GL_LIGHTING); glEnable (GL_LIGHT0); glEnable (GL_DEPTH_TEST); glClearColor (0.0, 0.0, 0.0, 0.0); glClearDepth (1.0); /* Create texture. */ gl_tex_create_texture (&texture); gdk_gl_drawable_gl_end (gldrawable); /*** OpenGL END ***/ /* Get PangoFT2 context. */ ft2_context = pango_ft2_get_context (72, 72); } static gboolean configure_event (GtkWidget *widget, GdkEventConfigure *event, gpointer data) { GdkGLContext *glcontext = gtk_widget_get_gl_context (widget); GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (widget); GLsizei w = widget->allocation.width; GLsizei h = widget->allocation.height; /*** OpenGL BEGIN ***/ if (!gdk_gl_drawable_gl_begin (gldrawable, glcontext)) return FALSE; glViewport (0, 0, w, h); glMatrixMode (GL_PROJECTION); glLoadIdentity (); gluPerspective (2.0 * FOVY_2, (GLfloat) w / (GLfloat) h, Z_NEAR, 2.5 * Z_NEAR); glMatrixMode (GL_MODELVIEW); glLoadIdentity (); gluLookAt (0.0, 0.0, Z_NEAR, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); glTranslatef (0.0, 0.0, -Z_NEAR); gdk_gl_drawable_gl_end (gldrawable); /*** OpenGL END ***/ return TRUE; } #define ANGLE 30.0 /* tan (ANGLE * PI / 180.0) */ #define TANGENT 0.57735 #define TEXT_Z_NEAR 2.0 #define TEXT_Z_FAR -5.0 #define TEXT_Z_DIFF 0.005 static GLfloat text_z = TEXT_Z_NEAR; static gboolean animate = TRUE; static gboolean expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer data) { GdkGLContext *glcontext = gtk_widget_get_gl_context (widget); GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (widget); PangoContext *widget_context; PangoFontDescription *font_desc; PangoLayout *layout; GLuint texture; GLfloat s0, s1, t0, t1; gboolean ret; /* Font */ widget_context = gtk_widget_get_pango_context (widget); font_desc = pango_context_get_font_description (widget_context); pango_font_description_set_size (font_desc, 24 * PANGO_SCALE); pango_context_set_font_description (ft2_context, font_desc); /* Text layout */ layout = pango_layout_new (ft2_context); pango_layout_set_width (layout, PANGO_SCALE * 200); pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER); pango_layout_set_text (layout, text, -1); /*** OpenGL BEGIN ***/ if (!gdk_gl_drawable_gl_begin (gldrawable, glcontext)) return FALSE; glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glCallList (1); /* Text color */ glColor3f (1.0, 0.9, 0.0); /* Render text */ ret = gl_tex_pango_ft2_render_layout (layout, &texture, &s0, &s1, &t0, &t1); if (ret) { glPushMatrix (); glTranslatef (0.0, -text_z * TANGENT, text_z + 2.0); glRotatef (ANGLE, 1.0, 0.0, 0.0); glEnable (GL_TEXTURE_2D); glEnable (GL_BLEND); glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBindTexture (GL_TEXTURE_2D, texture); glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glBegin (GL_QUADS); glTexCoord2f (s0, t0); glVertex3f (-1.0, 0.0, 1.0); glTexCoord2f (s0, t1); glVertex3f (-1.0, 0.0, -1.0); glTexCoord2f (s1, t1); glVertex3f ( 1.0, 0.0, -1.0); glTexCoord2f (s1, t0); glVertex3f ( 1.0, 0.0, 1.0); glEnd (); glDisable (GL_BLEND); glDisable (GL_TEXTURE_2D); glPopMatrix (); } if (gdk_gl_drawable_is_double_buffered (gldrawable)) gdk_gl_drawable_swap_buffers (gldrawable); else glFlush (); gdk_gl_drawable_gl_end (gldrawable); /*** OpenGL END ***/ g_object_unref (G_OBJECT (layout)); return TRUE; } static void unrealize (GtkWidget *widget, gpointer data) { GdkGLContext *glcontext = gtk_widget_get_gl_context (widget); GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (widget); /*** OpenGL BEGIN ***/ if (!gdk_gl_drawable_gl_begin (gldrawable, glcontext)) return; gl_tex_delete_texture (); gdk_gl_drawable_gl_end (gldrawable); /*** OpenGL END ***/ g_object_unref (G_OBJECT (ft2_context)); pango_ft2_shutdown_display (); } static gboolean timeout (GtkWidget *widget) { text_z -= TEXT_Z_DIFF; if (text_z <= TEXT_Z_FAR) text_z = TEXT_Z_NEAR; /* Invalidate the whole window. */ gdk_window_invalidate_rect (widget->window, &widget->allocation, FALSE); /* Update synchronously. */ gdk_window_process_updates (widget->window, FALSE); return TRUE; } static guint timeout_id = 0; static void timeout_add (GtkWidget *widget) { if (timeout_id == 0) { timeout_id = g_timeout_add (TIMEOUT_INTERVAL, (GSourceFunc) timeout, widget); } } static void timeout_remove (GtkWidget *widget) { if (timeout_id != 0) { g_source_remove (timeout_id); timeout_id = 0; } } static gboolean map_event (GtkWidget *widget, GdkEvent *event, gpointer data) { if (animate) timeout_add (widget); return TRUE; } static gboolean unmap_event (GtkWidget *widget, GdkEvent *event, gpointer data) { timeout_remove (widget); return TRUE; } static gboolean visibility_notify_event (GtkWidget *widget, GdkEventVisibility *event, gpointer data) { if (animate) { if (event->state == GDK_VISIBILITY_FULLY_OBSCURED) timeout_remove (widget); else timeout_add (widget); } return TRUE; } int main (int argc, char *argv[]) { GdkGLConfig *glconfig; GtkWidget *window; GtkWidget *vbox; GtkWidget *drawing_area; GtkWidget *button; /* * Init GTK. */ gtk_init (&argc, &argv); /* * Init GtkGLExt. */ gtk_gl_init (&argc, &argv); /* * Configure OpenGL-capable visual. */ /* Try double-buffered visual */ glconfig = gdk_gl_config_new_by_mode (GDK_GL_MODE_RGB | GDK_GL_MODE_DEPTH | GDK_GL_MODE_DOUBLE); if (glconfig == NULL) { g_print ("*** Cannot find the double-buffered visual.\n"); g_print ("*** Trying single-buffered visual.\n"); /* Try single-buffered visual */ glconfig = gdk_gl_config_new_by_mode (GDK_GL_MODE_RGB | GDK_GL_MODE_DEPTH); if (glconfig == NULL) { g_print ("*** No appropriate OpenGL-capable visual found.\n"); exit (1); } } /* * Top-level window. */ window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (window), "font-pangoft2"); /* Get automatically redrawn if any of their children changed allocation. */ gtk_container_set_reallocate_redraws (GTK_CONTAINER (window), TRUE); g_signal_connect (G_OBJECT (window), "delete_event", G_CALLBACK (gtk_main_quit), NULL); /* * VBox. */ vbox = gtk_vbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (window), vbox); gtk_widget_show (vbox); /* * Drawing area for drawing OpenGL scene. */ drawing_area = gtk_drawing_area_new (); gtk_widget_set_size_request (drawing_area, 200, 200); /* Set OpenGL-capability to the widget. */ gtk_widget_set_gl_capability (drawing_area, glconfig, NULL, TRUE, GDK_GL_RGBA_TYPE); g_signal_connect_after (G_OBJECT (drawing_area), "realize", G_CALLBACK (realize), NULL); g_signal_connect (G_OBJECT (drawing_area), "configure_event", G_CALLBACK (configure_event), NULL); g_signal_connect (G_OBJECT (drawing_area), "expose_event", G_CALLBACK (expose_event), NULL); g_signal_connect (G_OBJECT (drawing_area), "unrealize", G_CALLBACK (unrealize), NULL); g_signal_connect (G_OBJECT (drawing_area), "map_event", G_CALLBACK (map_event), NULL); g_signal_connect (G_OBJECT (drawing_area), "unmap_event", G_CALLBACK (unmap_event), NULL); g_signal_connect (G_OBJECT (drawing_area), "visibility_notify_event", G_CALLBACK (visibility_notify_event), NULL); gtk_box_pack_start (GTK_BOX (vbox), drawing_area, TRUE, TRUE, 0); gtk_widget_show (drawing_area); /* * Simple quit button. */ button = gtk_button_new_with_label ("Quit"); g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (gtk_main_quit), NULL); gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0); gtk_widget_show (button); /* * Show window. */ gtk_widget_show (window); /* * Main loop. */ gtk_main (); return 0; }