# HG changeset patch # User Michael Goffioul # Date 1246047129 -3600 # Node ID 4af6e29449c14def02fb10e4ae3adf69169a399e # Parent cdfb9ad48080ea3cfa95dbe241d539108215704a [mq]: graphics_text_engine diff --git a/ChangeLog b/ChangeLog --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2009-06-26 Michael Goffioul + + * 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 * run-octave.in: Exclude @-files from path. Remove CVS exclusions. diff --git a/aclocal.m4 b/aclocal.m4 --- a/aclocal.m4 +++ b/aclocal.m4 @@ -1464,3 +1464,167 @@ ;; esac ]) + +############################################################################## +############################################################################## + +# pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*- +# +# Copyright © 2004 Scott James Remnant . +# +# 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 .])], + [$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 + +############################################################################## +############################################################################## diff --git a/configure.in b/configure.in --- a/configure.in +++ b/configure.in @@ -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 diff --git a/src/ChangeLog b/src/ChangeLog --- a/src/ChangeLog +++ b/src/ChangeLog @@ -1,3 +1,27 @@ +2009-06-26 Michael Goffioul + + * 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 * load-path.cc (Faddpath): Preserve order of prepended elements. diff --git a/src/Makefile.in b/src/Makefile.in --- a/src/Makefile.in +++ b/src/Makefile.in @@ -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) diff --git a/src/gl-render.cc b/src/gl-render.cc --- a/src/gl-render.cc +++ b/src/gl-render.cc @@ -26,9 +26,13 @@ #if defined (HAVE_OPENGL) +#include + #include #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 (props)); else if (go.isa ("hggroup")) draw (dynamic_cast (props)); + else if (go.isa ("text")) + draw (dynamic_cast (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 (b(2))); + hmax = std::max (hmax, static_cast (b(3))); + } + } // minor grid lines if (do_xminorgrid) @@ -1026,6 +1054,8 @@ text::properties& xlabel_props = reinterpret_cast (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 (b(2))); + hmax = std::max (hmax, static_cast (b(3))); + } + } // minor grid lines if (do_yminorgrid) @@ -1213,6 +1264,8 @@ text::properties& ylabel_props = reinterpret_cast (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 (b(2))); + hmax = std::max (hmax, static_cast (b(3))); + } + } // minor grid lines if (do_zminorgrid) @@ -1457,6 +1531,8 @@ text::properties& zlabel_props = reinterpret_cast (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 obj_list; std::list::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 /* diff --git a/src/gl-render.h b/src/gl-render.h --- a/src/gl-render.h +++ b/src/gl-render.h @@ -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; }; diff --git a/src/graphics.cc b/src/graphics.cc --- a/src/graphics.cc +++ b/src/graphics.cc @@ -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 ()); diff --git a/src/txt-eng-ft.cc b/src/txt-eng-ft.cc new file mode 100644 --- /dev/null +++ b/src/txt-eng-ft.cc @@ -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 +. + +*/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#if HAVE_FREETYPE + +#if HAVE_FONTCONFIG +#include +#endif + +#include + +#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 (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 (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 (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 (c(0)*255); + green = static_cast (c(1)*255); + blue = static_cast (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 perm (3); + perm(0) = 0; + perm(1) = 2; + perm(2) = 1; + pixels = pixels.permute (perm); + + Array 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 (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 diff --git a/src/txt-eng-ft.h b/src/txt-eng-ft.h new file mode 100644 --- /dev/null +++ b/src/txt-eng-ft.h @@ -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 +. + +*/ + +#if ! defined (txt_eng_ft_h) +#define txt_eng_ft_h 1 + +#if HAVE_FREETYPE + +#include +#include FT_FREETYPE_H + +#include +#include +#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 diff --git a/src/txt-eng.h b/src/txt-eng.h new file mode 100644 --- /dev/null +++ b/src/txt-eng.h @@ -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 +. + +*/ + +#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 +{ +public: + text_element_list (void) + : text_element (), octave_base_list () { } + + ~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 (e)); } + + virtual void visit (text_superscript_element& e) + { visit (dynamic_cast (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