changeset 9403:4af6e29449c1

[mq]: graphics_text_engine
author Michael Goffioul <michael.goffioul@gmail.com>
date Fri, 26 Jun 2009 21:12:09 +0100
parents cdfb9ad48080
children 3dea4864921e
files ChangeLog aclocal.m4 configure.in src/ChangeLog src/Makefile.in src/gl-render.cc src/gl-render.h src/graphics.cc src/txt-eng-ft.cc src/txt-eng-ft.h src/txt-eng.h
diffstat 11 files changed, 1170 insertions(+), 11 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Fri Jun 26 20:49:41 2009 +0100
+++ b/ChangeLog	Fri Jun 26 21:12:09 2009 +0100
@@ -1,3 +1,9 @@
+2009-06-26  Michael Goffioul  <michael.goffioul@gmail.com>
+
+	* aclocal.m4: Add pkg.m4 macros.
+	* configure.in (HAVE_FREETYPE): New defined variable.
+	* configure.in: Add configure tests for Fontconfig detection.
+
 2009-06-23  Robert T. Short  <octave@phaselockedsystems.com>
 
 	* run-octave.in: Exclude @-files from path.  Remove CVS exclusions.
--- a/aclocal.m4	Fri Jun 26 20:49:41 2009 +0100
+++ b/aclocal.m4	Fri Jun 26 21:12:09 2009 +0100
@@ -1464,3 +1464,167 @@
   ;;
 esac
 ])
+
+##############################################################################
+##############################################################################
+
+# pkg.m4 - Macros to locate and utilise pkg-config.            -*- Autoconf -*-
+# 
+# Copyright © 2004 Scott James Remnant <scott@netsplit.com>.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# PKG_PROG_PKG_CONFIG([MIN-VERSION])
+# ----------------------------------
+AC_DEFUN([PKG_PROG_PKG_CONFIG],
+[m4_pattern_forbid([^_?PKG_[A-Z_]+$])
+m4_pattern_allow([^PKG_CONFIG(_PATH)?$])
+AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility])dnl
+if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then
+	AC_PATH_TOOL([PKG_CONFIG], [pkg-config])
+fi
+if test -n "$PKG_CONFIG"; then
+	_pkg_min_version=m4_default([$1], [0.9.0])
+	AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version])
+	if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then
+		AC_MSG_RESULT([yes])
+	else
+		AC_MSG_RESULT([no])
+		PKG_CONFIG=""
+	fi
+		
+fi[]dnl
+])# PKG_PROG_PKG_CONFIG
+
+# PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
+#
+# Check to see whether a particular set of modules exists.  Similar
+# to PKG_CHECK_MODULES(), but does not set variables or print errors.
+#
+#
+# Similar to PKG_CHECK_MODULES, make sure that the first instance of
+# this or PKG_CHECK_MODULES is called, or make sure to call
+# PKG_CHECK_EXISTS manually
+# --------------------------------------------------------------
+AC_DEFUN([PKG_CHECK_EXISTS],
+[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
+if test -n "$PKG_CONFIG" && \
+    AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then
+  m4_ifval([$2], [$2], [:])
+m4_ifvaln([$3], [else
+  $3])dnl
+fi])
+
+
+# _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES])
+# ---------------------------------------------
+m4_define([_PKG_CONFIG],
+[if test -n "$PKG_CONFIG"; then
+    if test -n "$$1"; then
+        pkg_cv_[]$1="$$1"
+    else
+        PKG_CHECK_EXISTS([$3],
+                         [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null`],
+			 [pkg_failed=yes])
+    fi
+else
+	pkg_failed=untried
+fi[]dnl
+])# _PKG_CONFIG
+
+# _PKG_SHORT_ERRORS_SUPPORTED
+# -----------------------------
+AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED],
+[AC_REQUIRE([PKG_PROG_PKG_CONFIG])
+if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
+        _pkg_short_errors_supported=yes
+else
+        _pkg_short_errors_supported=no
+fi[]dnl
+])# _PKG_SHORT_ERRORS_SUPPORTED
+
+
+# PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND],
+# [ACTION-IF-NOT-FOUND])
+#
+#
+# Note that if there is a possibility the first call to
+# PKG_CHECK_MODULES might not happen, you should be sure to include an
+# explicit call to PKG_PROG_PKG_CONFIG in your configure.ac
+#
+#
+# --------------------------------------------------------------
+AC_DEFUN([PKG_CHECK_MODULES],
+[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
+AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl
+AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl
+
+pkg_failed=no
+AC_MSG_CHECKING([for $1])
+
+_PKG_CONFIG([$1][_CFLAGS], [cflags], [$2])
+_PKG_CONFIG([$1][_LIBS], [libs], [$2])
+
+m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS
+and $1[]_LIBS to avoid the need to call pkg-config.
+See the pkg-config man page for more details.])
+
+if test $pkg_failed = yes; then
+        _PKG_SHORT_ERRORS_SUPPORTED
+        if test $_pkg_short_errors_supported = yes; then
+	        $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --errors-to-stdout --print-errors "$2"`
+        else 
+	        $1[]_PKG_ERRORS=`$PKG_CONFIG --errors-to-stdout --print-errors "$2"`
+        fi
+	# Put the nasty error message in config.log where it belongs
+	echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD
+
+	ifelse([$4], , [AC_MSG_ERROR(dnl
+[Package requirements ($2) were not met:
+
+$$1_PKG_ERRORS
+
+Consider adjusting the PKG_CONFIG_PATH environment variable if you
+installed software in a non-standard prefix.
+
+_PKG_TEXT
+])],
+		[AC_MSG_RESULT([no])
+                $4])
+elif test $pkg_failed = untried; then
+	ifelse([$4], , [AC_MSG_FAILURE(dnl
+[The pkg-config script could not be found or is too old.  Make sure it
+is in your PATH or set the PKG_CONFIG environment variable to the full
+path to pkg-config.
+
+_PKG_TEXT
+
+To get pkg-config, see <http://pkg-config.freedesktop.org/>.])],
+		[$4])
+else
+	$1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS
+	$1[]_LIBS=$pkg_cv_[]$1[]_LIBS
+        AC_MSG_RESULT([yes])
+	ifelse([$3], , :, [$3])
+fi[]dnl
+])# PKG_CHECK_MODULES
+
+##############################################################################
+##############################################################################
--- a/configure.in	Fri Jun 26 20:49:41 2009 +0100
+++ b/configure.in	Fri Jun 26 21:12:09 2009 +0100
@@ -687,7 +687,7 @@
   AC_DEFINE(HAVE_OPENGL, 1, [Define if OpenGL is available])
 
 ## ftgl (needs freetype 2)
-  AC_CHECK_FT2([9.0.3],[],
+  AC_CHECK_FT2([9.0.3],[AC_DEFINE(HAVE_FREETYPE, 1, [Define to 1 if you have Freetype library.])],
     [warn_freetype="FreeType library not found. Native renderer will not have on-screen text"])
   if test -z "$warn_freetype"; then
     AC_LANG_PUSH(C++)
@@ -742,6 +742,20 @@
   fi
 fi
 
+# fontconfig library
+
+warn_fontconfig=""
+PKG_PROG_PKG_CONFIG
+if test -z "$warn_freetype"; then
+  PKG_CHECK_MODULES(FONTCONFIG,[fontconfig],[
+    have_fontconfig=yes
+    OPENGL_LIBS="$FONTCONFIG_LIBS $OPENGL_LIBS"
+    XTRA_CXXFLAGS="$XTRA_CXXFLAGS $FONTCONFIG_CFLAGS"
+    AC_DEFINE(HAVE_FONTCONFIG, 1, [Define to 1 if fontconfig is present])],[
+    have_fontconfig=no
+    warn_fontconfig="Fontconfig not found. Native text rendering will use hard-coded font instead."])
+fi
+
 GRAPHICS_LIBS=
 GRAPHICS_CFLAGS=
 
@@ -2436,6 +2450,12 @@
   warn_msg_printed=true
 fi
 
+if test -n "$warn_fontconfig"; then
+  AC_MSG_WARN("$warn_fontconfig")
+  native_graphics=false
+  warn_msg_printed=true
+fi
+
 if test -n "$warn_ftgl"; then
   AC_MSG_WARN("$warn_ftgl")
   native_graphics=false
--- a/src/ChangeLog	Fri Jun 26 20:49:41 2009 +0100
+++ b/src/ChangeLog	Fri Jun 26 21:12:09 2009 +0100
@@ -1,3 +1,27 @@
+2009-06-26  Michael Goffioul  <michael.goffioul@gmail.com>
+
+	* txt-eng.h: New file for simple text engine.
+	* txt-eng.h, txt-eng.cc: Freetype based text render engine.
+	* Makefile.in: Add txt-eng-ft.cc to list of source files.
+	* gl-render.h (opengl_renderer::draw_text, opengl_renderer::set_font,
+	opengl_renderer::draw(text::properties)): New method to support text
+	rendering using the Freetype renderer.
+	(opengl_renderer::text_renderer): New field for text rendering.
+	* gl-render.cc (opengl_renderer::draw(graphics_object)): Support text
+	object.
+	(opengl_renderer::draw(figure::properties)): Setup alpha test.
+	(opengl_renderer::draw(axes::properties)): Render tick labels, hide
+	labels for depth axes and fix a problem in calling
+	graphics_object::get(char*).
+	(opengl_renderer::draw(text::properties)): Basic text rendering using
+	Freetype engine.
+	(opengl_renderer::draw_text): Ditto.
+	(opengl_renderer::set_color): Propagate the color to the text
+	renderer.
+	(opengl_renderer::set_font): New utility method.
+	* graphics.cc (axes::properties::init, axes::properties::set_defauls):
+	(Re)initialize tick labels, labels and title property.
+
 2009-06-26  John W. Eaton  <jwe@octave.org>
 
 	* load-path.cc (Faddpath): Preserve order of prepended elements.
--- a/src/Makefile.in	Fri Jun 26 20:49:41 2009 +0100
+++ b/src/Makefile.in	Fri Jun 26 21:12:09 2009 +0100
@@ -230,6 +230,7 @@
 	parse.y pr-output.cc procstream.cc sighandlers.cc \
 	siglist.c sparse-xdiv.cc sparse-xpow.cc strfns.cc \
 	syscalls.cc symtab.cc sysdep.cc token.cc toplev.cc \
+	txt-eng-ft.cc \
 	unwind-prot.cc utils.cc variables.cc xdiv.cc xnorm.cc xpow.cc \
 	$(OV_SRC) \
 	$(PT_SRC)
--- a/src/gl-render.cc	Fri Jun 26 20:49:41 2009 +0100
+++ b/src/gl-render.cc	Fri Jun 26 21:12:09 2009 +0100
@@ -26,9 +26,13 @@
 
 #if defined (HAVE_OPENGL)
 
+#include <iostream>
+
 #include <lo-mappers.h>
 #include "oct-locbuf.h"
 #include "gl-render.h"
+#include "txt-eng.h"
+#include "txt-eng-ft.h"
 
 #define LIGHT_MODE GL_FRONT_AND_BACK
 
@@ -540,6 +544,8 @@
     draw (dynamic_cast<const patch::properties&> (props));
   else if (go.isa ("hggroup"))
     draw (dynamic_cast<const hggroup::properties&> (props));
+  else if (go.isa ("text"))
+    draw (dynamic_cast<const text::properties&> (props));
   else
     warning ("opengl_renderer: cannot render object of type `%s'",
 	     props.graphics_object_name ().c_str ());
@@ -555,6 +561,7 @@
   glEnable (GL_DEPTH_TEST);
   glDepthFunc (GL_LEQUAL);
   glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+  glAlphaFunc (GL_GREATER, 0.0f);
   glEnable (GL_NORMALIZE);
 
   if (props.is___enhanced__ ())
@@ -874,6 +881,8 @@
   std::string gridstyle = props.get_gridlinestyle ();
   std::string minorgridstyle = props.get_minorgridlinestyle ();
 
+  set_font (props);
+
   // X grid
 
   if (visible && xstate != AXE_DEPTH_DIR)
@@ -884,8 +893,7 @@
       Matrix xticks = xform.xscale (props.get_xtick ().matrix_value ());
       // FIXME: use pre-computed minor ticks
       Matrix xmticks;
-      // FIXME: use xticklabels property
-      string_vector xticklabels;
+      string_vector xticklabels = props.get_xticklabel ().all_strings ();
       int wmax = 0, hmax = 0;
       bool tick_along_z = xisinf (fy);
       Matrix tickpos (xticks.numel (), 3);
@@ -957,7 +965,27 @@
           glEnd ();
         }
 
-      // FIXME: tick texts
+      // tick texts
+      if (xticklabels.numel () > 0)
+	{
+	  int n = std::min (xticklabels.numel (), xticks.numel ());
+	  int halign = (xstate == AXE_HORZ_DIR ? 1 : (xySym ? 0 : 2));
+	  int valign = (xstate == AXE_VERT_DIR
+			? 1
+		       : (zd*zv(2) <= 0 && !x2Dtop ? 2 : 0));
+
+	  for (int i = 0; i < n; i++)
+	    {
+	      // FIXME: as tick text is transparent, shouldn't be
+	      //        drawn after axes object, for correct rendering?
+	      Matrix b = draw_text (xticklabels(i),
+				    tickpos(i,0), tickpos(i,1), tickpos(i,2),
+				    halign, valign); 
+
+	      wmax = std::max (wmax, static_cast<int> (b(2)));
+	      hmax = std::max (hmax, static_cast<int> (b(3)));
+	    }
+	}
 
       // minor grid lines
       if (do_xminorgrid)
@@ -1026,6 +1054,8 @@
       text::properties& xlabel_props =
         reinterpret_cast<text::properties&> (gh_manager::get_object (props.get_xlabel ()).get_properties ());
 
+      xlabel_props.set_visible ("on");
+
       // FIXME: auto-positioning should be disabled if the 
       //        label has been positioned manually
       if (! xlabel_props.get_string ().empty ())
@@ -1060,6 +1090,10 @@
           xlabel_props.set_rotation (angle);
         }
     }
+  else
+    {
+      gh_manager::get_object (props.get_xlabel ()).set ("visible", "off");
+    }
 
   // Y grid
 		
@@ -1071,8 +1105,7 @@
       Matrix yticks = xform.yscale (props.get_ytick ().matrix_value ());
       // FIXME: use pre-computed minor ticks
       Matrix ymticks;
-      // FIXME: use yticklabels property
-      string_vector yticklabels;
+      string_vector yticklabels = props.get_yticklabel ().all_strings ();
       int wmax = 0, hmax = 0;
       bool tick_along_z = xisinf (fx);
       Matrix tickpos (yticks.numel (), 3);
@@ -1144,7 +1177,25 @@
           glEnd ();
         }
 
-      // FIXME: tick texts
+      // tick texts
+      if (yticklabels.numel () > 0)
+	{
+	  int n = std::min (yticklabels.numel (), yticks.numel ());
+	  int halign = (ystate == AXE_HORZ_DIR ? 1 : (!xySym || y2Dright ? 0 : 2));
+	  int valign = (ystate == AXE_VERT_DIR ? 1 : (zd*zv(2) <= 0 ? 2 : 0));
+
+	  for (int i = 0; i < n; i++)
+	    {
+	      // FIXME: as tick text is transparent, shouldn't be
+	      //        drawn after axes object, for correct rendering?
+	      Matrix b = draw_text (yticklabels(i),
+				    tickpos(i,0), tickpos(i,1), tickpos(i,2),
+				    halign, valign); 
+
+	      wmax = std::max (wmax, static_cast<int> (b(2)));
+	      hmax = std::max (hmax, static_cast<int> (b(3)));
+	    }
+	}
 
       // minor grid lines
       if (do_yminorgrid)
@@ -1213,6 +1264,8 @@
       text::properties& ylabel_props =
         reinterpret_cast<text::properties&> (gh_manager::get_object (props.get_ylabel ()).get_properties ());
 
+      ylabel_props.set_visible ("on");
+
       // FIXME: auto-positioning should be disabled if the 
       //        label has been positioned manually
       if (! ylabel_props.get_string ().empty ())
@@ -1247,6 +1300,10 @@
           ylabel_props.set_rotation (angle);
         }
     }
+  else
+    {
+      gh_manager::get_object (props.get_ylabel ()).set ("visible", "off");
+    }
 		
   // Z Grid
 
@@ -1258,8 +1315,7 @@
       Matrix zticks = xform.zscale (props.get_ztick ().matrix_value ());
       // FIXME: use pre-computed minor ticks
       Matrix zmticks;
-      // FIXME: use zticklabels property
-      string_vector zticklabels;
+      string_vector zticklabels = props.get_zticklabel ().all_strings ();
       int wmax = 0, hmax = 0;
       Matrix tickpos (zticks.numel (), 3);
 
@@ -1364,6 +1420,24 @@
         }
 
       // FIXME: tick texts
+      if (zticklabels.numel () > 0)
+	{
+	  int n = std::min (zticklabels.numel (), zticks.numel ());
+	  int halign = 2;
+	  int valign = (zstate == AXE_VERT_DIR ? 1 : (zd*zv(2) < 0 ? 3 : 2));
+
+	  for (int i = 0; i < n; i++)
+	    {
+	      // FIXME: as tick text is transparent, shouldn't be
+	      //        drawn after axes object, for correct rendering?
+	      Matrix b = draw_text (zticklabels(i),
+				    tickpos(i,0), tickpos(i,1), tickpos(i,2),
+				    halign, valign); 
+
+	      wmax = std::max (wmax, static_cast<int> (b(2)));
+	      hmax = std::max (hmax, static_cast<int> (b(3)));
+	    }
+	}
 
       // minor grid lines
       if (do_zminorgrid)
@@ -1457,6 +1531,8 @@
       text::properties& zlabel_props =
         reinterpret_cast<text::properties&> (gh_manager::get_object (props.get_zlabel ()).get_properties ());
 
+      zlabel_props.set_visible ("on");
+
       // FIXME: auto-positioning should be disabled if the 
       //        label has been positioned manually
       if (! zlabel_props.get_string ().empty ())
@@ -1512,6 +1588,10 @@
           zlabel_props.set_rotation (angle);
         }
     }
+  else
+    {
+      gh_manager::get_object (props.get_zlabel ()).set ("visible", "off");
+    }
 
   set_linestyle ("-");
 
@@ -1537,7 +1617,7 @@
   if (antialias == GL_TRUE)
     glEnable (GL_LINE_SMOOTH);
 
-  Matrix children = props.get_children ();
+  Matrix children = props.get_all_children ();
   std::list<graphics_object> obj_list;
   std::list<graphics_object>::iterator it;
 
@@ -1564,7 +1644,7 @@
       graphics_object go = (*it);
 
       // FIXME: check whether object has "units" property and it is set to "data"
-      if (! go.isa ("text") || go.get ("units").string_value () == "data")
+      if (! go.isa ("text") || go.get (caseless_str ("units")).string_value () == "data")
         {
           set_clipping (go.get_properties ().is_clipping ());
           draw (go);
@@ -2572,6 +2652,38 @@
 }
 
 void
+opengl_renderer::draw (const text::properties& props)
+{
+  if (props.get_string ().empty ())
+    return;
+
+  set_font (props);
+  set_color (props.get_color_rgb ());
+
+  // FIXME: take "units" into account
+  Matrix pos = props.get_position ().matrix_value ();
+  int halign = 0, valign = 0;
+
+  if (props.horizontalalignment_is ("center"))
+    halign = 1;
+  else if (props.horizontalalignment_is ("right"))
+    halign = 2;
+  
+  if (props.verticalalignment_is ("top"))
+    valign = 2;
+  else if (props.verticalalignment_is ("baseline"))
+    valign = 3;
+  else if (props.verticalalignment_is ("middle"))
+    valign = 1;
+
+  // FIXME: handle margin and surrounding box
+
+  draw_text (props.get_string (),
+	     pos(0), pos(1), pos(2),
+	     halign, valign, props.get_rotation ());
+}
+
+void
 opengl_renderer::set_viewport (int w, int h)
 {
   glViewport (0, 0, w, h);
@@ -2581,6 +2693,17 @@
 opengl_renderer::set_color (const Matrix& c)
 {
   glColor3dv (c.data ());
+#if HAVE_FREETYPE
+  text_renderer.set_color (c);
+#endif
+}
+
+void
+opengl_renderer::set_font (const base_properties& props)
+{
+#if HAVE_FREETYPE
+  text_renderer.set_font (props);
+#endif
 }
 
 void
@@ -2869,6 +2992,85 @@
   return ID;
 }
 
+Matrix
+opengl_renderer::draw_text (const std::string& txt,
+			    double x, double y, double z,
+			    int halign, int valign, double rotation)
+{
+#if HAVE_FREETYPE
+  if (txt.empty ())
+    return Matrix (1, 4, 0.0);
+
+  // FIXME: clip "rotation" between 0 and 360
+
+  int rot_mode = ft_render::ROTATION_0;
+
+  if (rotation == 90.0)
+    rot_mode = ft_render::ROTATION_90;
+  else if (rotation == 180.0)
+    rot_mode = ft_render::ROTATION_180;
+  else if (rotation == 270.0)
+    rot_mode = ft_render::ROTATION_270;
+
+  text_element *elt = text_parser_none ().parse (txt);
+  Matrix bbox;
+  uint8NDArray pixels = text_renderer.render (elt, bbox, rot_mode);
+  int x0 = 0, y0 = 0;
+  int w = bbox(2), h = bbox(3);
+
+  switch (halign)
+    {
+    default: break;
+    case 1: x0 = -bbox(2)/2; break;
+    case 2: x0 = -bbox(2); break;
+    }
+  switch (valign)
+    {
+    default: break;
+    case 1: y0 = -bbox(3)/2; break;
+    case 2: y0 = -bbox(3); break;
+    case 3: y0 = bbox(1); break;
+    }
+
+  switch (rot_mode)
+    {
+    case ft_render::ROTATION_90:
+      std::swap (x0, y0);
+      std::swap (w, h);
+      x0 -= bbox(3);
+      break;
+    case ft_render::ROTATION_180:
+      x0 -= bbox(2);
+      y0 -= bbox(3);
+      break;
+    case ft_render::ROTATION_270:
+      std::swap (x0, y0);
+      std::swap (w, h);
+      y0 -= bbox(2);
+      break;
+    }
+
+  bool blend = glIsEnabled (GL_BLEND);
+
+  glEnable (GL_BLEND);
+  glEnable (GL_ALPHA_TEST);
+  glRasterPos3d (x, y, z);
+  glBitmap(0, 0, 0, 0, x0, y0, 0);
+  glDrawPixels (w, h,
+		GL_RGBA, GL_UNSIGNED_BYTE, pixels.data ());
+  glDisable (GL_ALPHA_TEST);
+  if (! blend)
+    glDisable (GL_BLEND);
+
+  delete elt;
+
+  return bbox;
+#else
+  ::error ("draw_text: cannot render text, Freetype library not available");
+  return Matrix (1, 4, 0.0);
+#endif
+}
+
 #endif
 
 /*
--- a/src/gl-render.h	Fri Jun 26 20:49:41 2009 +0100
+++ b/src/gl-render.h	Fri Jun 26 21:12:09 2009 +0100
@@ -40,6 +40,7 @@
 #endif
 
 #include "graphics.h"
+#include "txt-eng-ft.h"
 
 class
 OCTINTERP_API
@@ -77,6 +78,7 @@
   virtual void draw (const surface::properties& props);
   virtual void draw (const patch::properties& props);
   virtual void draw (const hggroup::properties& props);
+  virtual void draw (const text::properties& props);
 
   virtual void set_color (const Matrix& c);
   virtual void set_polygon_offset (bool on, double offset = 0.0);
@@ -85,12 +87,17 @@
   virtual void set_clipbox (double x1, double x2, double y1, double y2,
 			    double z1, double z2);
   virtual void set_clipping (bool on);
+  virtual void set_font (const base_properties& props);
 
   virtual void init_marker (const std::string& m, double size, float width);
   virtual void end_marker (void);
   virtual void draw_marker (double x, double y, double z,
 			    const Matrix& lc, const Matrix& fc);
 
+  virtual Matrix draw_text (const std::string& txt,
+			    double x, double y, double z,
+			    int halign, int valign, double rotation = 0.0);
+
 private:
   opengl_renderer (const opengl_renderer&) { }
 
@@ -138,6 +145,11 @@
   // camera information for primitive sorting
   ColumnVector camera_pos, camera_dir;
 
+#if HAVE_FREETYPE
+  // freetype render, used for text rendering
+  ft_render text_renderer;
+#endif
+
 private:
   class patch_tesselator;
 };
--- a/src/graphics.cc	Fri Jun 26 20:49:41 2009 +0100
+++ b/src/graphics.cc	Fri Jun 26 21:12:09 2009 +0100
@@ -2389,6 +2389,10 @@
   sy = "linear";
   sz = "linear";
 
+  calc_ticklabels (xtick, xticklabel, xscale.is ("log"));
+  calc_ticklabels (ytick, yticklabel, yscale.is ("log"));
+  calc_ticklabels (ztick, zticklabel, zscale.is ("log"));
+
   xset (xlabel.handle_value (), "handlevisibility", "off");
   xset (ylabel.handle_value (), "handlevisibility", "off");
   xset (zlabel.handle_value (), "handlevisibility", "off");
@@ -2404,6 +2408,12 @@
   xset (title.handle_value (), "verticalalignment", "bottom");
 
   xset (ylabel.handle_value (), "rotation", 90.0);
+  xset (zlabel.handle_value (), "visible", "off");
+  
+  xset (xlabel.handle_value (), "clipping", "off");
+  xset (ylabel.handle_value (), "clipping", "off");
+  xset (zlabel.handle_value (), "clipping", "off");
+  xset (title.handle_value (), "clipping", "off");
 
   adopt (xlabel.handle_value ());
   adopt (ylabel.handle_value ());
@@ -2622,6 +2632,12 @@
   xset (title.handle_value (), "verticalalignment", "bottom");
 
   xset (ylabel.handle_value (), "rotation", 90.0);
+  xset (zlabel.handle_value (), "visible", "off");
+  
+  xset (xlabel.handle_value (), "clipping", "off");
+  xset (ylabel.handle_value (), "clipping", "off");
+  xset (zlabel.handle_value (), "clipping", "off");
+  xset (title.handle_value (), "clipping", "off");
 
   adopt (xlabel.handle_value ());
   adopt (ylabel.handle_value ());
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/txt-eng-ft.cc	Fri Jun 26 21:12:09 2009 +0100
@@ -0,0 +1,439 @@
+/*
+
+Copyright (C) 2009 Michael Goffioul
+
+This file is part of Octave.
+
+Octave is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 3 of the License, or (at your
+option) any later version.
+
+Octave is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with Octave; see the file COPYING.  If not, see
+<http://www.gnu.org/licenses/>.
+
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#if HAVE_FREETYPE
+
+#if HAVE_FONTCONFIG
+#include <fontconfig/fontconfig.h>
+#endif
+
+#include <iostream>
+
+#include "error.h"
+#include "pr-output.h"
+#include "txt-eng-ft.h"
+
+class
+ft_manager
+{
+public:
+  static bool instance_ok (void)
+    {
+      bool retval = true;
+
+      if (! instance)
+	instance = new ft_manager ();
+
+      if (! instance)
+	{
+	  ::error ("unable to create ft_manager!");
+
+	  retval = false;
+	}
+
+      return retval;
+    }
+
+  static FT_Face get_font (const std::string& name, const std::string& weight,
+			   const std::string& angle, double size)
+    { return (instance_ok ()
+	      ? instance->do_get_font (name, weight, angle, size)
+	      : 0); }
+
+private:
+  static ft_manager *instance;
+
+private:
+  ft_manager (void)
+    {
+      if (FT_Init_FreeType (&library))
+	{
+	  ::error ("unable to initialize freetype library");
+	}
+
+#if HAVE_FONTCONFIG
+      fc_init_done = false;
+      if (! FcInit ())
+	{
+	  ::error ("unable to initialize fontconfig library");
+	}
+      else
+	{
+	  fc_init_done = true;
+	}
+#endif
+    }
+
+  ~ft_manager (void)
+    {
+#if HAVE_FONTCONFIG
+      FcFini ();
+      fc_init_done = false;
+#endif
+    }
+
+  FT_Face do_get_font (const std::string& name, const std::string& weight,
+		       const std::string& angle, double size)
+    {
+      FT_Face retval = 0;
+
+      std::string file;
+
+#if HAVE_FONTCONFIG
+      if (fc_init_done)
+	{
+	  int fc_weight, fc_angle;
+
+	  if (weight == "bold")
+	    fc_weight = FC_WEIGHT_BOLD;
+	  else if (weight == "light")
+	    fc_weight = FC_WEIGHT_LIGHT;
+	  else if (weight == "demi")
+	    fc_weight = FC_WEIGHT_DEMIBOLD;
+	  else
+	    fc_weight = FC_WEIGHT_NORMAL;
+
+	  if (angle == "italic")
+	    fc_angle = FC_SLANT_ITALIC;
+	  else if (angle == "oblique")
+	    fc_angle = FC_SLANT_OBLIQUE;
+	  else
+	    fc_angle = FC_SLANT_ROMAN;
+
+	  FcPattern *pat = FcPatternCreate ();
+
+	  FcPatternAddString (pat, FC_FAMILY, reinterpret_cast<const FcChar8*> (name == "*" ? "sans" : name.c_str ()));
+	  FcPatternAddInteger (pat, FC_WEIGHT, fc_weight);
+	  FcPatternAddInteger (pat, FC_SLANT, fc_angle);
+	  FcPatternAddDouble (pat, FC_PIXEL_SIZE, size);
+
+	  if (FcConfigSubstitute (0, pat, FcMatchPattern))
+	    {
+	      FcResult res;
+	      FcPattern *match;
+
+	      FcDefaultSubstitute (pat);
+	      match = FcFontMatch (0, pat, &res);
+	      
+	      if (match && res != FcResultNoMatch)
+		{
+		  unsigned char *tmp;
+
+		  FcPatternGetString (match, FC_FILE, 0, &tmp);
+		  file = reinterpret_cast<char*> (tmp);
+		}
+	      else
+		::error ("could not match any font: %s-%s-%s-%g",
+			 name.c_str (), weight.c_str (), angle.c_str (),
+			 size);
+
+	      if (match)
+		FcPatternDestroy (match);
+	    }
+
+	  FcPatternDestroy (pat);
+	}
+#endif
+
+      if (file.empty ())
+	{
+#ifdef __WIN32__
+	  file = "C:/WINDOWS/Fonts/verdana.ttf";
+#else
+	  // FIXME: find a "standard" font for UNIX platforms
+#endif
+	}
+
+      if (FT_New_Face (library, file.c_str (), 0, &retval))
+	{
+	  ::error ("unable to load font: %s", file.c_str ());
+	}
+
+      
+      return retval;
+    }
+
+private:
+  FT_Library library;
+#if HAVE_FONTCONFIG
+  bool fc_init_done;
+#endif
+};
+
+ft_manager* ft_manager::instance = 0;
+
+// ---------------------------------------------------------------------------
+
+ft_render::ft_render (void)
+    : text_processor (), face (0), bbox (1, 4, 0.0),
+      xoffset (0), yoffset (0), mode (MODE_BBOX),
+      red (0), green (0), blue (0)
+{
+}
+
+ft_render::~ft_render (void)
+{
+  if (face)
+    FT_Done_Face (face);
+}
+
+void
+ft_render::set_font (const base_properties& props)
+{
+  if (face)
+    FT_Done_Face (face);
+
+  // FIXME: take "fontunits" into account
+  double font_size = props.get (caseless_str ("fontsize")).double_value ();
+
+  face = ft_manager::get_font (props.get (caseless_str ("fontname")).string_value (),
+			       props.get (caseless_str ("fontweight")).string_value (),
+			       props.get (caseless_str ("fontangle")).string_value (),
+			       font_size);
+
+  if (face)
+    {
+      if (FT_Set_Char_Size (face, 0, font_size*64, 0, 0))
+	{
+	  ::error ("ft_render: unable to set font size to %d", font_size);
+	}
+    }
+  else
+    ::error ("ft_render: unable to load appropriate font");
+}
+
+void
+ft_render::set_mode (int m)
+{
+  mode = m;
+
+  switch (mode)
+    {
+    case MODE_BBOX:
+      xoffset = yoffset = 0;
+      bbox = Matrix (1, 4, 0.0);
+      break;
+    case MODE_RENDER:
+      if (bbox.numel () != 4)
+	{
+	  ::error ("ft_render: invalid bounding box, cannot render");
+
+	  xoffset = yoffset = 0;
+	  pixels = uint8NDArray ();
+	}
+      else
+	{
+	  pixels = uint8NDArray (dim_vector (4, bbox(2), bbox(3)),
+				 static_cast<uint8_t> (0));
+	  xoffset = 0;
+	  yoffset = -bbox(1)-1;
+	}
+      break;
+    default:
+      ::error ("ft_render: invalid mode `%d'", mode);
+      break;
+    }
+}
+
+void
+ft_render::visit (text_element_string& e)
+{
+  if (! face)
+    {
+      ::error ("ft_render: font not initialized");
+      return;
+    }
+
+  std::string str = e.string_value ();
+  FT_UInt glyph_index, previous = 0;
+
+  for (int i = 0; i < str.length (); i++)
+    {
+      glyph_index = FT_Get_Char_Index (face, str[i]);
+
+      if (! glyph_index || FT_Load_Glyph (face, glyph_index, FT_LOAD_DEFAULT))
+	{
+	  ::error ("ft_render: unable to load glyph from font for character `%c', skipping",
+		   str[i]);
+	}
+      else
+	{
+	  switch (mode)
+	    {
+	    case MODE_RENDER:
+	      if (FT_Render_Glyph (face->glyph, FT_RENDER_MODE_NORMAL))
+		{
+		  ::error ("ft_render: unable to render glyph for character `%c', skipping",
+			   str[i]);
+		}
+	      else
+		{
+		  FT_Bitmap& bitmap = face->glyph->bitmap;
+		  int x0, y0;
+
+		  if (previous)
+		    {
+		      FT_Vector delta;
+
+		      FT_Get_Kerning (face, previous, glyph_index, FT_KERNING_DEFAULT, &delta);
+		      xoffset += (delta.x >> 6);
+		    }
+
+		  x0 = xoffset+face->glyph->bitmap_left;
+		  y0 = yoffset+face->glyph->bitmap_top;
+		  for (int r = 0; r < bitmap.rows; r++)
+		    for (int c = 0; c < bitmap.width; c++)
+		      {
+			unsigned char pix = bitmap.buffer[r*bitmap.width+c];
+			if (x0+c < 0 || x0+c >= pixels.dim2()
+			    || y0-r < 0 || y0-r >= pixels.dim3())
+			  {
+			    //::error ("out-of-bound indexing!!");
+			  }
+			else if (pixels(3, x0+c, y0-r).value () == 0)
+			  {
+			    pixels(0, x0+c, y0-r) = red;
+			    pixels(1, x0+c, y0-r) = green;
+			    pixels(2, x0+c, y0-r) = blue;
+			    pixels(3, x0+c, y0-r) = pix;
+			  }
+		      }
+
+		  xoffset += (face->glyph->advance.x >> 6);
+		}
+	      break;
+
+	    case MODE_BBOX:
+	      // width
+	      if (previous)
+		{
+		  FT_Vector delta;
+
+		  FT_Get_Kerning (face, previous, glyph_index, FT_KERNING_DEFAULT, &delta);
+		  bbox(2) += (delta.x >> 6);
+		}
+	      bbox(2) += (face->glyph->advance.x >> 6);
+
+	      int asc, desc;
+
+	      if (false /*tight*/)
+		{
+		  desc = face->glyph->metrics.horiBearingY - face->glyph->metrics.height;
+		  asc = face->glyph->metrics.horiBearingY;
+		}
+	      else
+		{
+		  asc = face->size->metrics.ascender;
+		  desc = face->size->metrics.descender;
+		}
+
+	      asc = yoffset + (asc >> 6);
+	      desc = yoffset + (desc >> 6);
+
+	      if (desc < bbox(1))
+		{
+		  bbox(3) += (bbox(1) - desc);
+		  bbox(1) = desc;
+		}
+	      if (asc > (bbox(3)+bbox(1)))
+		bbox(3) = asc-bbox(1);
+	      break;
+	    }
+	  
+	  previous = glyph_index;
+	}
+    }
+}
+
+void
+ft_render::reset (void)
+{
+  set_mode (MODE_BBOX);
+  set_color (Matrix (1, 3, 0.0));
+}
+
+void
+ft_render::set_color (Matrix c)
+{
+  if (c.numel () == 3)
+    {
+      red = static_cast<uint8_t> (c(0)*255);
+      green = static_cast<uint8_t> (c(1)*255);
+      blue = static_cast<uint8_t> (c(2)*255);
+    }
+  else
+    ::error ("ft_render::set_color: invalid color");
+}
+
+uint8NDArray
+ft_render::render (text_element* elt, Matrix& box, int rotation)
+{
+  set_mode (MODE_BBOX);
+  elt->accept (*this);
+  box = bbox;
+
+  set_mode (MODE_RENDER);
+  elt->accept (*this);
+
+  switch (rotation)
+    {
+    case ROTATION_0:
+      break;
+    case ROTATION_90:
+	{
+	  Array<octave_idx_type> perm (3);
+	  perm(0) = 0;
+	  perm(1) = 2;
+	  perm(2) = 1;
+	  pixels = pixels.permute (perm);
+
+	  Array<idx_vector> idx (3);
+	  idx(0) = idx_vector (':');
+	  idx(1) = idx_vector (pixels.dim2()-1, -1, -1);
+	  idx(2) = idx_vector (':');
+	  pixels = uint8NDArray (pixels.index (idx));
+	}
+      break;
+    case ROTATION_180:
+	{
+	  Array<idx_vector> idx (3);
+	  idx(0) = idx_vector (':');
+	  idx(1) = idx_vector (pixels.dim2()-1, 0, -1);
+	  idx(2)=  idx_vector (':');
+	  pixels = uint8NDArray (pixels.index (idx));
+	}
+      break;
+    case ROTATION_270:
+	{
+	  // FIXME: implement this...
+	}
+      break;
+    }
+
+  return pixels;
+}
+
+#endif // HAVE_FREETYPE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/txt-eng-ft.h	Fri Jun 26 21:12:09 2009 +0100
@@ -0,0 +1,87 @@
+/*
+
+Copyright (C) 2009 Michael Goffioul
+
+This file is part of Octave.
+
+Octave is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 3 of the License, or (at your
+option) any later version.
+
+Octave is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with Octave; see the file COPYING.  If not, see
+<http://www.gnu.org/licenses/>.
+
+*/
+
+#if ! defined (txt_eng_ft_h)
+#define txt_eng_ft_h 1
+
+#if HAVE_FREETYPE
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+
+#include <dMatrix.h>
+#include <uint8NDArray.h>
+#include "graphics.h"
+#include "txt-eng.h"
+
+class
+OCTINTERP_API
+ft_render : public text_processor
+{
+public:
+  enum {
+      MODE_BBOX   = 0,
+      MODE_RENDER = 1
+  };
+
+  enum {
+      ROTATION_0   = 0,
+      ROTATION_90  = 1,
+      ROTATION_180 = 2,
+      ROTATION_270 = 3
+  };
+
+public:
+  ft_render (void);
+
+  ~ft_render (void);
+
+  void visit (text_element_string& e);
+
+  void reset (void);
+
+  uint8NDArray get_pixels (void) const { return pixels; }
+
+  Matrix get_boundingbox (void) const { return bbox; }
+
+  uint8NDArray render (text_element* elt, Matrix& box,
+		       int rotation = ROTATION_0);
+
+  void set_font (const base_properties& props);
+
+  void set_color (Matrix c);
+
+  void set_mode (int m);
+
+private:
+  FT_Face face;
+  Matrix bbox;
+  uint8NDArray pixels;
+  int xoffset;
+  int yoffset;  
+  int mode;
+  uint8_t red, green, blue;
+};
+
+#endif // HAVE_FREETYPE
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/txt-eng.h	Fri Jun 26 21:12:09 2009 +0100
@@ -0,0 +1,188 @@
+/*
+
+Copyright (C) 2009 Michael Goffioul
+
+This file is part of Octave.
+
+Octave is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 3 of the License, or (at your
+option) any later version.
+
+Octave is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with Octave; see the file COPYING.  If not, see
+<http://www.gnu.org/licenses/>.
+
+*/
+
+#if ! defined (txt_eng_h)
+#define txt_eng_h 1
+
+#include "base-list.h"
+
+class text_element;
+class text_element_string;
+class text_element_list;
+class text_subscript_element;
+class text_superscript_element;
+
+class text_processor;
+
+class
+OCTINTERP_API
+text_element
+{
+public:
+  text_element (void) { }
+
+  virtual ~text_element (void) { }
+
+  virtual void accept (text_processor& p) = 0;
+
+private:
+  text_element (const text_element&);
+};
+
+class
+OCTINTERP_API
+text_element_string : public text_element
+{
+public:
+  text_element_string (const std::string& s = "")
+      : text_element (), str (s) { }
+
+  ~text_element_string (void) { }
+
+  std::string string_value (void) const { return str; }
+
+  void accept (text_processor& p);
+
+private:
+  std::string str;
+
+private:
+  text_element_string (const text_element_string &);
+};
+
+class
+OCTINTERP_API
+text_element_list :
+    public text_element,
+    public octave_base_list<text_element *>
+{
+public:
+  text_element_list (void)
+      : text_element (), octave_base_list<text_element*> () { }
+
+  ~text_element_list (void)
+    {
+      while (! empty ())
+	{
+	  iterator it = begin ();
+	  delete (*it);
+	  erase (it);
+	}
+    }
+
+  void accept (text_processor& p);
+};
+
+class
+OCTINTERP_API
+text_subscript_element : public text_element_list
+{
+public:
+  text_subscript_element (void)
+      : text_element_list () { }
+
+  ~text_subscript_element (void) { }
+
+  void accept (text_processor& p);
+};
+
+class
+OCTINTERP_API
+text_superscript_element : public text_element_list
+{
+public:
+  text_superscript_element (void)
+      : text_element_list () { }
+
+  ~text_superscript_element (void) { }
+
+  void accept (text_processor& p);
+};
+
+class
+OCTINTERP_API
+text_processor
+{
+public:
+  virtual void visit (text_element_string& e) = 0;
+
+  virtual void visit (text_element_list& e)
+    {
+      for (text_element_list::iterator it = e.begin ();
+	   it != e.end (); ++it)
+	{
+	  (*it)->accept (*this);
+	}
+    }
+  
+  virtual void visit (text_subscript_element& e)
+    { visit (dynamic_cast<text_element_list&> (e)); }
+  
+  virtual void visit (text_superscript_element& e)
+    { visit (dynamic_cast<text_element_list&> (e)); }
+
+  virtual void reset (void) { }
+
+protected:
+  text_processor (void) { }
+
+  virtual ~text_processor (void) { } 
+};
+
+#define TEXT_ELEMENT_ACCEPT(cls) \
+inline void \
+cls::accept (text_processor& p) \
+{ p.visit (*this); }
+
+TEXT_ELEMENT_ACCEPT(text_element_string)
+TEXT_ELEMENT_ACCEPT(text_element_list)
+TEXT_ELEMENT_ACCEPT(text_subscript_element)
+TEXT_ELEMENT_ACCEPT(text_superscript_element)
+
+class
+OCTINTERP_API
+text_parser
+{
+public:
+  text_parser (void) { }
+
+  virtual ~text_parser (void) { }
+
+  virtual text_element* parse (const std::string& s) = 0;
+};
+
+class
+OCTINTERP_API
+text_parser_none : public text_parser
+{
+public:
+  text_parser_none (void) : text_parser () { }
+
+  ~text_parser_none (void) { }
+
+  text_element* parse (const std::string& s)
+    {
+      return new text_element_string (s);
+    }
+};
+
+#endif