# HG changeset patch # User John W. Eaton # Date 1454764553 18000 # Node ID 67d2965af0b577a536a6711f550c38cad49fec5a # Parent f5e05c11c34355b502f86954bc9120c6c10fb57d revamp text rendering classes * base-text-renderer.h: New file. * ft-text-renderer.h, ft-text-renderer.cc: New files for freetype text rendering classes, adapted from txt-eng-ft.h and txt-eng.cc. * text-renderer.h, text-renderer.cc: New files. Public interface for text rendering. * gl-select.cc, gl-render.cc, gl-render.h, gl2ps-print.cc, graphics.cc, graphics.in.h: Adapt to use new text rendering interface that does not require checking HAVE_FREETYPE. * libinterp/corefcn/module.mk: Update. diff -r f5e05c11c343 -r 67d2965af0b5 libgui/graphics/gl-select.cc --- a/libgui/graphics/gl-select.cc Fri Feb 05 14:53:10 2016 -0500 +++ b/libgui/graphics/gl-select.cc Sat Feb 06 08:15:53 2016 -0500 @@ -187,19 +187,15 @@ double x, double y, double z, int halign, int valign, double rotation) { -#if HAVE_FREETYPE uint8NDArray pixels; - Matrix bbox; + Matrix bbox (1, 4, 0.0); // FIXME: probably more efficient to only compute bbox instead // of doing full text rendering... text_to_pixels (txt, pixels, bbox, halign, valign, rotation); - fake_text (x, y, z, bbox, false); + fake_text(x, y, z, bbox, false); return bbox; -#else - return Matrix (1, 4, 0.0); -#endif } void diff -r f5e05c11c343 -r 67d2965af0b5 libinterp/corefcn/base-text-renderer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libinterp/corefcn/base-text-renderer.h Sat Feb 06 08:15:53 2016 -0500 @@ -0,0 +1,79 @@ +/* + +Copyright (C) 2016 John W. Eaton +Copyright (C) 2009-2015 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 (octave_base_text_renderer_h) +#define octave_base_text_renderer_h 1 + +#include +#include + +#include "dMatrix.h" +#include "uint8NDArray.h" + +#include "text-renderer.h" +#include "txt-eng.h" + +class +base_text_renderer : public text_processor +{ +public: + + base_text_renderer (void) : text_processor () { } + + virtual ~base_text_renderer (void) { } + + virtual Matrix + get_extent (text_element *elt, double rotation) = 0; + + virtual Matrix + get_extent (const std::string& txt, double rotation, + const caseless_str& interpreter) = 0; + + virtual void + set_font (const std::string& name, const std::string& weight, + const std::string& angle, double size) = 0; + + virtual void set_color (const Matrix& c) = 0; + + virtual void + text_to_pixels (const std::string& txt, uint8NDArray& pxls, + Matrix& bbox, int halign, int valign, double rotation, + const caseless_str& interpreter, + bool handle_rotation) = 0; + + virtual void + text_to_strlist (const std::string& txt, + std::list& lst, + Matrix& box, int halign, int valign, double rotation, + const caseless_str& interpreter = "tex") = 0; + +private: + + // No copying! + + base_text_renderer (const base_text_renderer&); + + base_text_renderer& operator = (const base_text_renderer&); +}; + +#endif diff -r f5e05c11c343 -r 67d2965af0b5 libinterp/corefcn/ft-text-renderer.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libinterp/corefcn/ft-text-renderer.cc Sat Feb 06 08:15:53 2016 -0500 @@ -0,0 +1,1367 @@ +/* + +Copyright (C) 2016 John W. Eaton +Copyright (C) 2009-2015 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 + +#include "base-text-renderer.h" + +#if defined (HAVE_FREETYPE) + +#include +#include FT_FREETYPE_H + +#if defined (HAVE_FONTCONFIG) +# include +#endif + +#include +#include +#include +#include +#include + +#include "singleton-cleanup.h" + +#include "error.h" +#include "pr-output.h" +#include "text-renderer.h" + +// FIXME: maybe issue at most one warning per glyph/font/size/weight +// combination. + +static void +warn_missing_glyph (FT_ULong c) +{ + warning_with_id ("Octave:missing-glyph", + "text_renderer: skipping missing glyph for character '%x'", c); +} + +static void +warn_glyph_render (FT_ULong c) +{ + warning_with_id ("Octave:glyph-render", + "text_renderer: unable to render glyph for character '%x'", c); +} + +#if defined (_MSC_VER) +// FIXME: is this really needed? +// +// This is just a trick to avoid multiple symbol definitions. +// PermMatrix.h contains a dllexport'ed Array +// that will cause MSVC not to generate a new instantiation and +// use the imported one instead. +# include "PermMatrix.h" +#endif + +// Forward declaration +static void ft_face_destroyed (void *object); + +class +ft_manager +{ +public: + static bool instance_ok (void) + { + bool retval = true; + + if (! instance) + { + instance = new ft_manager (); + + if (instance) + singleton_cleanup_list::add (cleanup_instance); + } + + if (! instance) + error ("unable to create ft_manager!"); + + return retval; + } + + static void cleanup_instance (void) { delete instance; instance = 0; } + + 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); + } + + static void font_destroyed (FT_Face face) + { + if (instance_ok ()) + instance->do_font_destroyed (face); + } + +private: + + static ft_manager *instance; + + typedef std::pair ft_key; + typedef std::map ft_cache; + + // Cache the fonts loaded by FreeType. This cache only contains + // weak references to the fonts, strong references are only present + // in class text_renderer. + ft_cache cache; + +private: + + // No copying! + + ft_manager (const ft_manager&); + + ft_manager& operator = (const ft_manager&); + + ft_manager (void) + : library (), freetype_initialized (false), fontconfig_initialized (false) + { + if (FT_Init_FreeType (&library)) + error ("unable to initialize FreeType library"); + else + freetype_initialized = true; + +#if defined (HAVE_FONTCONFIG) + if (! FcInit ()) + error ("unable to initialize fontconfig library"); + else + fontconfig_initialized = true; +#endif + } + + ~ft_manager (void) + { + if (freetype_initialized) + FT_Done_FreeType (library); + +#if defined (HAVE_FONTCONFIG) + // FIXME: Skip the call to FcFini because it can trigger the assertion + // + // octave: fccache.c:507: FcCacheFini: Assertion 'fcCacheChains[i] == ((void *)0)' failed. + // + // if (fontconfig_initialized) + // FcFini (); +#endif + } + + + FT_Face do_get_font (const std::string& name, const std::string& weight, + const std::string& angle, double size) + { + FT_Face retval = 0; + +#if defined (HAVE_FT_REFERENCE_FACE) + // Look first into the font cache, then use fontconfig. If the font + // is present in the cache, simply add a reference and return it. + + ft_key key (name + ":" + weight + ":" + angle, size); + ft_cache::const_iterator it = cache.find (key); + + if (it != cache.end ()) + { + FT_Reference_Face (it->second); + return it->second; + } +#endif + + std::string file; + +#if defined (HAVE_FONTCONFIG) + if (fontconfig_initialized) + { + 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); + + // FIXME: originally, this test also required that + // res != FcResultNoMatch. Is that really needed? + if (match) + { + unsigned char *tmp; + + FcPatternGetString (match, FC_FILE, 0, &tmp); + file = reinterpret_cast (tmp); + } + else + ::warning ("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 ()) + { +#if defined (__WIN32__) + file = "C:/WINDOWS/Fonts/verdana.ttf"; +#else + // FIXME: find a "standard" font for UNIX platforms +#endif + } + + if (! file.empty ()) + { + if (FT_New_Face (library, file.c_str (), 0, &retval)) + ::warning ("ft_manager: unable to load font: %s", file.c_str ()); +#if defined (HAVE_FT_REFERENCE_FACE) + else + { + // Install a finalizer to notify ft_manager that the font is + // being destroyed. The class ft_manager only keeps weak + // references to font objects. + + retval->generic.data = new ft_key (key); + retval->generic.finalizer = ft_face_destroyed; + + // Insert loaded font into the cache. + + cache[key] = retval; + } +#endif + } + + return retval; + } + + void do_font_destroyed (FT_Face face) + { + if (face->generic.data) + { + ft_key *pkey = reinterpret_cast (face->generic.data); + + cache.erase (*pkey); + delete pkey; + face->generic.data = 0; + } + } + +private: + FT_Library library; + bool freetype_initialized; + bool fontconfig_initialized; +}; + +ft_manager *ft_manager::instance = 0; + +static void +ft_face_destroyed (void *object) +{ ft_manager::font_destroyed (reinterpret_cast (object)); } + +// --------------------------------------------------------------------------- + +class +OCTINTERP_API +ft_text_renderer : public base_text_renderer +{ +public: + + enum + { + MODE_BBOX = 0, + MODE_RENDER = 1 + }; + + enum + { + ROTATION_0 = 0, + ROTATION_90 = 1, + ROTATION_180 = 2, + ROTATION_270 = 3 + }; + +public: + + ft_text_renderer (void) + : base_text_renderer (), font (), bbox (1, 4, 0.0), halign (0), + xoffset (0), line_yoffset (0), yoffset (0), mode (MODE_BBOX), + color (dim_vector (1, 3), 0) + { } + + ~ft_text_renderer (void) { } + + void visit (text_element_string& e); + + void visit (text_element_list& e); + + void visit (text_element_subscript& e); + + void visit (text_element_superscript& e); + + void visit (text_element_color& e); + + void visit (text_element_fontsize& e); + + void visit (text_element_fontname& e); + + void visit (text_element_fontstyle& e); + + void visit (text_element_symbol& e); + + void visit (text_element_combined& 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); + + Matrix get_extent (text_element *elt, double rotation = 0.0); + Matrix get_extent (const std::string& txt, double rotation, + const caseless_str& interpreter); + + void set_font (const std::string& name, const std::string& weight, + const std::string& angle, double size); + + void set_color (const Matrix& c); + + void set_mode (int m); + + void text_to_pixels (const std::string& txt, + uint8NDArray& pxls, Matrix& bbox, + int halign, int valign, double rotation, + const caseless_str& interpreter, + bool handle_rotation); + +private: + + int rotation_to_mode (double rotation) const; + + // No copying! + + ft_text_renderer (const ft_text_renderer&); + + ft_text_renderer& operator = (const ft_text_renderer&); + + // Class to hold information about fonts and a strong + // reference to the font objects loaded by FreeType. + + class ft_font : public text_renderer::font + { + public: + + ft_font (void) + : text_renderer::font (), face (0) { } + + ft_font (const std::string& nm, const std::string& wt, + const std::string& ang, double sz, FT_Face f = 0) + : text_renderer::font (nm, wt, ang, sz), face (f) + { } + + ft_font (const ft_font& ft); + + ~ft_font (void) + { + if (face) + FT_Done_Face (face); + } + + ft_font& operator = (const ft_font& ft); + + bool is_valid (void) const { return get_face (); } + + FT_Face get_face (void) const; + + private: + + mutable FT_Face face; + }; + + void push_new_line (void); + + void update_line_bbox (void); + + void compute_bbox (void); + + int compute_line_xoffset (const Matrix& lb) const; + + FT_UInt process_character (FT_ULong code, FT_UInt previous = 0); + +public: + + void text_to_strlist (const std::string& txt, + std::list& lst, Matrix& bbox, + int halign, int valign, double rotation, + const caseless_str& interp); + +private: + + // The current font used by the renderer. + ft_font font; + + // Used to stored the bounding box corresponding to the rendered text. + // The bounding box has the form [x, y, w, h] where x and y represent the + // coordinates of the bottom left corner relative to the anchor point of + // the text (== start of text on the baseline). Due to font descent or + // multiple lines, the value y is usually negative. + Matrix bbox; + + // Used to stored the rendered text. It's a 3D matrix with size MxNx4 + // where M and N are the width and height of the bounding box. + uint8NDArray pixels; + + // Used to store the bounding box of each line. This is used to layout + // multiline text properly. + std::list line_bbox; + + // The current horizontal alignment. This is used to align multi-line text. + int halign; + + // The X offset for the next glyph. + int xoffset; + + // The Y offset of the baseline for the current line. + int line_yoffset; + + // The Y offset of the baseline for the next glyph. The offset is relative + // to line_yoffset. The total Y offset is computed with: + // line_yoffset + yoffset. + int yoffset; + + // The current mode of the rendering process (box computing or rendering). + int mode; + + // The base color of the rendered text. + uint8NDArray color; + + // A list of parsed strings to be used for printing. + std::list strlist; + + // The X offset of the baseline for the current line. + int line_xoffset; + +}; + +void +ft_text_renderer::set_font (const std::string& name, const std::string& weight, + const std::string& angle, double size) +{ + // FIXME: take "fontunits" into account + + font = ft_font (name, weight, angle, size, 0); +} + +void +ft_text_renderer::push_new_line (void) +{ + switch (mode) + { + case MODE_BBOX: + { + // Create a new bbox entry based on the current font. + + FT_Face face = font.get_face (); + + if (face) + { + int asc = face->size->metrics.ascender >> 6; + int desc = face->size->metrics.descender >> 6; + int h = face->size->metrics.height >> 6; + + Matrix bb (1, 5, 0.0); + + bb(1) = desc; + bb(3) = asc - desc; + bb(4) = h; + + line_bbox.push_back (bb); + + xoffset = yoffset = 0; + } + } + break; + + case MODE_RENDER: + { + // Move to the next line bbox, adjust xoffset based on alignment + // and yoffset based on the old and new line bbox. + + Matrix old_bbox = line_bbox.front (); + line_bbox.pop_front (); + Matrix new_bbox = line_bbox.front (); + + xoffset = line_xoffset = compute_line_xoffset (new_bbox); + line_yoffset += (old_bbox(1) - (new_bbox(1) + new_bbox(3))); + yoffset = 0; + } + break; + } +} + +int +ft_text_renderer::compute_line_xoffset (const Matrix& lb) const +{ + if (! bbox.is_empty ()) + { + switch (halign) + { + case 0: + return 0; + case 1: + return (bbox(2) - lb(2)) / 2; + case 2: + return (bbox(2) - lb(2)); + } + } + + return 0; +} + +void +ft_text_renderer::compute_bbox (void) +{ + // Stack the various line bbox together and compute the final + // bounding box for the entire text string. + + bbox = Matrix (); + + switch (line_bbox.size ()) + { + case 0: + break; + + case 1: + bbox = line_bbox.front ().extract (0, 0, 0, 3); + break; + + default: + for (std::list::const_iterator it = line_bbox.begin (); + it != line_bbox.end (); ++it) + { + if (bbox.is_empty ()) + bbox = it->extract (0, 0, 0, 3); + else + { + bbox(1) -= (*it)(3); + bbox(3) += (*it)(3); + bbox(2) = xmax (bbox(2), (*it)(2)); + } + } + break; + } +} + +void +ft_text_renderer::update_line_bbox (void) +{ + // Called after a font change, when in MODE_BBOX mode, to update the + // current line bbox with the new font metrics. This also includes the + // current yoffset, that is the offset of the current glyph's baseline + // the line's baseline. + + if (mode == MODE_BBOX) + { + int asc = font.get_face ()->size->metrics.ascender >> 6; + int desc = font.get_face ()->size->metrics.descender >> 6; + + Matrix& bb = line_bbox.back (); + + if ((yoffset + desc) < bb(1)) + { + // The new font goes below the bottom of the current bbox. + + int delta = bb(1) - (yoffset + desc); + + bb(1) -= delta; + bb(3) += delta; + } + + if ((yoffset + asc) > (bb(1) + bb(3))) + { + // The new font goes above the top of the current bbox. + + int delta = (yoffset + asc) - (bb(1) + bb(3)); + + bb(3) += delta; + } + } +} + +void +ft_text_renderer::set_mode (int m) +{ + mode = m; + + switch (mode) + { + case MODE_BBOX: + xoffset = line_yoffset = yoffset = 0; + bbox = Matrix (1, 4, 0.0); + line_bbox.clear (); + push_new_line (); + break; + + case MODE_RENDER: + if (bbox.numel () != 4) + { + ::warning ("ft_text_renderer: invalid bounding box, cannot render"); + + xoffset = line_yoffset = yoffset = 0; + pixels = uint8NDArray (); + } + else + { + pixels = uint8NDArray (dim_vector (4, bbox(2), bbox(3)), + static_cast (0)); + xoffset = compute_line_xoffset (line_bbox.front ()); + line_yoffset = -bbox(1)-1; + yoffset = 0; + } + break; + + default: + error ("ft_text_renderer: invalid mode '%d'", mode); + break; + } +} + +FT_UInt +ft_text_renderer::process_character (FT_ULong code, FT_UInt previous) +{ + FT_Face face = font.get_face (); + FT_UInt glyph_index = 0; + + if (face) + { + glyph_index = FT_Get_Char_Index (face, code); + + if (code != '\n' + && (! glyph_index + || FT_Load_Glyph (face, glyph_index, FT_LOAD_DEFAULT))) + { + glyph_index = 0; + warn_missing_glyph (code); + } + else + { + switch (mode) + { + case MODE_RENDER: + if (code == '\n') + { + glyph_index = FT_Get_Char_Index (face, ' '); + if (! glyph_index + || FT_Load_Glyph (face, glyph_index, FT_LOAD_DEFAULT)) + { + glyph_index = 0; + warn_missing_glyph (' '); + } + else + push_new_line (); + } + else if (FT_Render_Glyph (face->glyph, FT_RENDER_MODE_NORMAL)) + { + glyph_index = 0; + warn_glyph_render (code); + } + 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 = line_yoffset + yoffset + face->glyph->bitmap_top; + + // 'w' seems to have a negative -1 + // face->glyph->bitmap_left, this is so we don't + // index out of bound, and assumes we we allocated + // the right amount of horizontal space in the bbox. + if (x0 < 0) + x0 = 0; + + for (int r = 0; static_cast (r) < bitmap.rows; r++) + for (int c = 0; static_cast (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 ()) + { + //::warning ("ft_text_renderer: pixel out of bound (char=%d, (x,y)=(%d,%d), (w,h)=(%d,%d)", + // str[i], x0+c, y0-r, pixels.dim2 (), pixels.dim3 ()); + } + else if (pixels(3, x0+c, y0-r).value () == 0) + { + pixels(0, x0+c, y0-r) = color(0); + pixels(1, x0+c, y0-r) = color(1); + pixels(2, x0+c, y0-r) = color(2); + pixels(3, x0+c, y0-r) = pix; + } + } + + xoffset += (face->glyph->advance.x >> 6); + } + break; + + case MODE_BBOX: + if (code == '\n') + { + glyph_index = FT_Get_Char_Index (face, ' '); + if (! glyph_index + || FT_Load_Glyph (face, glyph_index, FT_LOAD_DEFAULT)) + { + glyph_index = 0; + warn_missing_glyph (' '); + } + else + push_new_line (); + } + else + { + Matrix& bb = line_bbox.back (); + + // If we have a previous glyph, use kerning information. + // This usually means moving a bit backward before adding + // the next glyph. That is, "delta.x" is usually < 0. + if (previous) + { + FT_Vector delta; + + FT_Get_Kerning (face, previous, glyph_index, + FT_KERNING_DEFAULT, &delta); + + xoffset += (delta.x >> 6); + } + + // Extend current X offset box by the width of the current + // glyph. Then extend the line bounding box if necessary. + + xoffset += (face->glyph->advance.x >> 6); + bb(2) = xmax (bb(2), xoffset); + } + break; + } + } + } + + return glyph_index; +} + +void +ft_text_renderer::text_to_strlist (const std::string& txt, + std::list& lst, + Matrix& box, + int ha, int va, double rot, + const caseless_str& interp) +{ + uint8NDArray pxls; + + // First run text_to_pixels which will also build the string list + + text_to_pixels (txt, pxls, box, ha, va, rot, interp, false); + + lst = strlist; +} + +void +ft_text_renderer::visit (text_element_string& e) +{ + if (font.is_valid ()) + { + FT_UInt glyph_index, previous = 0; + + std::string str = e.string_value (); + size_t n = str.length (); + size_t curr = 0; + size_t idx = 0; + mbstate_t ps; + memset (&ps, 0, sizeof (ps)); // Initialize state to 0. + wchar_t wc; + + text_renderer::string fs (str, font, xoffset, yoffset); + + while (n > 0) + { + size_t r = gnulib::mbrtowc (&wc, str.data () + curr, n, &ps); + + if (r > 0 + && r != static_cast (-1) + && r != static_cast (-2)) + { + n -= r; + curr += r; + + if (wc == L'\n') + { + // Finish previous string in srtlist before processing + // the newline character + fs.set_y (line_yoffset + yoffset); + fs.set_color (color); + std::string s = str.substr (idx, curr - idx - 1); + if (! s.empty ()) + { + fs.set_string (s); + strlist.push_back (fs); + } + } + + glyph_index = process_character (wc, previous); + + if (wc == L'\n') + { + previous = 0; + // Start a new string in strlist + idx = curr; + fs = text_renderer::string (str.substr (idx), font, + line_xoffset, yoffset); + + } + else + previous = glyph_index; + } + else + { + if (r != 0) + ::warning ("ft_text_renderer: failed to decode string `%s' with " + "locale `%s'", str.c_str (), + std::setlocale (LC_CTYPE, 0)); + break; + } + } + + if (! fs.get_string ().empty ()) + { + fs.set_y (line_yoffset + yoffset); + fs.set_color (color); + strlist.push_back (fs); + } + } +} + +void +ft_text_renderer::visit (text_element_list& e) +{ + // Save and restore (after processing the list) the current font and color. + + ft_font saved_font (font); + uint8NDArray saved_color (color); + + text_processor::visit (e); + + font = saved_font; + color = saved_color; +} + +void +ft_text_renderer::visit (text_element_subscript& e) +{ + ft_font saved_font (font); + int saved_line_yoffset = line_yoffset; + int saved_yoffset = yoffset; + + set_font (font.get_name (), font.get_weight (), font.get_angle (), + font.get_size () - 2); + + if (font.is_valid ()) + { + int h = font.get_face ()->size->metrics.height >> 6; + + // Shifting the baseline by 2/3 the font height seems to produce + // decent result. + yoffset -= (h * 2) / 3; + + if (mode == MODE_BBOX) + update_line_bbox (); + } + + text_processor::visit (e); + + font = saved_font; + // If line_yoffset changed, this means we moved to a new line; hence yoffset + // cannot be restored, because the saved value is not relevant anymore. + if (line_yoffset == saved_line_yoffset) + yoffset = saved_yoffset; +} + +void +ft_text_renderer::visit (text_element_superscript& e) +{ + ft_font saved_font (font); + int saved_line_yoffset = line_yoffset; + int saved_yoffset = yoffset; + + set_font (font.get_name (), font.get_weight (), font.get_angle (), + font.get_size () - 2); + + if (saved_font.is_valid ()) + { + int s_asc = saved_font.get_face ()->size->metrics.ascender >> 6; + + // Shifting the baseline by 2/3 base font ascender seems to produce + // decent result. + yoffset += (s_asc * 2) / 3; + + if (mode == MODE_BBOX) + update_line_bbox (); + } + + text_processor::visit (e); + + font = saved_font; + // If line_yoffset changed, this means we moved to a new line; hence yoffset + // cannot be restored, because the saved value is not relevant anymore. + if (line_yoffset == saved_line_yoffset) + yoffset = saved_yoffset; +} + +void +ft_text_renderer::visit (text_element_color& e) +{ + if (mode == MODE_RENDER) + set_color (e.get_color ()); +} + +void +ft_text_renderer::visit (text_element_fontsize& e) +{ + double sz = e.get_fontsize (); + + // FIXME: Matlab documentation says that the font size is expressed + // in the text object FontUnit. + + set_font (font.get_name (), font.get_weight (), font.get_angle (), sz); + + if (mode == MODE_BBOX) + update_line_bbox (); +} + +void +ft_text_renderer::visit (text_element_fontname& e) +{ + set_font (e.get_fontname (), font.get_weight (), font.get_angle (), + font.get_size ()); + + if (mode == MODE_BBOX) + update_line_bbox (); +} + +void +ft_text_renderer::visit (text_element_fontstyle& e) +{ + switch (e.get_fontstyle ()) + { + case text_element_fontstyle::normal: + set_font (font.get_name (), "normal", "normal", font.get_size ()); + break; + + case text_element_fontstyle::bold: + set_font (font.get_name (), "bold", "normal", font.get_size ()); + break; + + case text_element_fontstyle::italic: + set_font (font.get_name (), "normal", "italic", font.get_size ()); + break; + + case text_element_fontstyle::oblique: + set_font (font.get_name (), "normal", "oblique", font.get_size ()); + break; + } + + if (mode == MODE_BBOX) + update_line_bbox (); +} + +void +ft_text_renderer::visit (text_element_symbol& e) +{ + uint32_t code = e.get_symbol_code (); + + text_renderer::string fs (std::string ("-"), font, xoffset, yoffset); + + if (code != text_element_symbol::invalid_code && font.is_valid ()) + { + process_character (code); + fs.set_code (code); + } + else if (font.is_valid ()) + ::warning ("ignoring unknown symbol: %d", e.get_symbol ()); + + if (fs.get_code ()) + { + fs.set_y (line_yoffset + yoffset); + fs.set_color (color); + strlist.push_back (fs); + } +} + +void +ft_text_renderer::visit (text_element_combined& e) +{ + int saved_xoffset = xoffset; + int max_xoffset = xoffset; + + for (text_element_combined::iterator it = e.begin (); it != e.end (); ++it) + { + xoffset = saved_xoffset; + (*it)->accept (*this); + max_xoffset = xmax (xoffset, max_xoffset); + } + + xoffset = max_xoffset; +} + +void +ft_text_renderer::reset (void) +{ + set_mode (MODE_BBOX); + set_color (Matrix (1, 3, 0.0)); +} + +void +ft_text_renderer::set_color (const Matrix& c) +{ + if (c.numel () == 3) + { + color(0) = static_cast (c(0)*255); + color(1) = static_cast (c(1)*255); + color(2) = static_cast (c(2)*255); + } + else + ::warning ("ft_text_renderer::set_color: invalid color"); +} + +uint8NDArray +ft_text_renderer::render (text_element *elt, Matrix& box, int rotation) +{ + set_mode (MODE_BBOX); + elt->accept (*this); + compute_bbox (); + box = bbox; + + set_mode (MODE_RENDER); + // Clear the list of parsed strings + strlist.clear (); + + if (pixels.numel () > 0) + { + elt->accept (*this); + + switch (rotation) + { + case ROTATION_0: + break; + + case ROTATION_90: + { + Array perm (dim_vector (3, 1)); + perm(0) = 0; + perm(1) = 2; + perm(2) = 1; + pixels = pixels.permute (perm); + + Array idx (dim_vector (3, 1)); + 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 (dim_vector (3, 1)); + idx(0) = idx_vector (':'); + idx(1) = idx_vector (pixels.dim2 ()-1, -1, -1); + idx(2)= idx_vector (pixels.dim3 ()-1, -1, -1); + pixels = uint8NDArray (pixels.index (idx)); + } + break; + + case ROTATION_270: + { + Array perm (dim_vector (3, 1)); + perm(0) = 0; + perm(1) = 2; + perm(2) = 1; + pixels = pixels.permute (perm); + + Array idx (dim_vector (3, 1)); + idx(0) = idx_vector (':'); + idx(1) = idx_vector (':'); + idx(2) = idx_vector (pixels.dim3 ()-1, -1, -1); + pixels = uint8NDArray (pixels.index (idx)); + } + break; + } + } + + return pixels; +} + +// Note: +// x-extent accurately measures width of glyphs. +// y-extent is overly large because it is measured from baseline-to-baseline. +// Calling routines, such as ylabel, may need to account for this mismatch. + +Matrix +ft_text_renderer::get_extent (text_element *elt, double rotation) +{ + set_mode (MODE_BBOX); + elt->accept (*this); + compute_bbox (); + + Matrix extent (1, 2, 0.0); + + switch (rotation_to_mode (rotation)) + { + case ROTATION_0: + case ROTATION_180: + extent(0) = bbox(2); + extent(1) = bbox(3); + break; + + case ROTATION_90: + case ROTATION_270: + extent(0) = bbox(3); + extent(1) = bbox(2); + } + + return extent; +} + +Matrix +ft_text_renderer::get_extent (const std::string& txt, double rotation, + const caseless_str& interpreter) +{ + text_element *elt = text_parser::parse (txt, interpreter); + Matrix extent = get_extent (elt, rotation); + delete elt; + + return extent; +} + +int +ft_text_renderer::rotation_to_mode (double rotation) const +{ + // Clip rotation to range [0, 360] + while (rotation < 0) + rotation += 360.0; + while (rotation > 360.0) + rotation -= 360.0; + + if (rotation == 0.0) + return ROTATION_0; + else if (rotation == 90.0) + return ROTATION_90; + else if (rotation == 180.0) + return ROTATION_180; + else if (rotation == 270.0) + return ROTATION_270; + else + return ROTATION_0; +} + +void +ft_text_renderer::text_to_pixels (const std::string& txt, + uint8NDArray& pxls, Matrix& box, + int _halign, int valign, double rotation, + const caseless_str& interpreter, + bool handle_rotation) +{ + int rot_mode = rotation_to_mode (rotation); + + halign = _halign; + + text_element *elt = text_parser::parse (txt, interpreter); + pxls = render (elt, box, rot_mode); + delete elt; + + if (pxls.is_empty ()) + return; // nothing to render + + switch (halign) + { + case 1: + box(0) = -box(2)/2; + break; + + case 2: + box(0) = -box(2); + break; + + default: + box(0) = 0; + break; + } + + switch (valign) + { + case 1: + box(1) = -box(3)/2; + break; + + case 2: + box(1) = -box(3); + break; + + case 3: + break; + + case 4: + box(1) = -box(3)-box(1); + break; + + default: + box(1) = 0; + break; + } + + if (handle_rotation) + { + switch (rot_mode) + { + case ROTATION_90: + std::swap (box(0), box(1)); + std::swap (box(2), box(3)); + box(0) = -box(0)-box(2); + break; + + case ROTATION_180: + box(0) = -box(0)-box(2); + box(1) = -box(1)-box(3); + break; + + case ROTATION_270: + std::swap (box(0), box(1)); + std::swap (box(2), box(3)); + box(1) = -box(1)-box(3); + break; + } + } +} + +ft_text_renderer::ft_font::ft_font (const ft_font& ft) + : text_renderer::font (ft), face (0) +{ +#if defined (HAVE_FT_REFERENCE_FACE) + FT_Face ft_face = ft.get_face (); + + if (ft_face && FT_Reference_Face (ft_face) == 0) + face = ft_face; +#endif +} + +ft_text_renderer::ft_font& +ft_text_renderer::ft_font::operator = (const ft_font& ft) +{ + if (&ft != this) + { + text_renderer::font::operator = (ft); + + if (face) + { + FT_Done_Face (face); + face = 0; + } + +#if defined (HAVE_FT_REFERENCE_FACE) + FT_Face ft_face = ft.get_face (); + + if (ft_face && FT_Reference_Face (ft_face) == 0) + face = ft_face; +#endif + } + + return *this; +} + +FT_Face +ft_text_renderer::ft_font::get_face (void) const +{ + if (! face && ! name.empty ()) + { + face = ft_manager::get_font (name, weight, angle, size); + + if (face) + { + if (FT_Set_Char_Size (face, 0, size*64, 0, 0)) + ::warning ("ft_text_renderer: unable to set font size to %g", size); + } + else + ::warning ("ft_text_renderer: unable to load appropriate font"); + } + + return face; +} + +#endif + +base_text_renderer * +make_ft_text_renderer (void) +{ +#if defined (HAVE_FREETYPE) + return new ft_text_renderer (); +#else + return 0; +#endif +} diff -r f5e05c11c343 -r 67d2965af0b5 libinterp/corefcn/ft-text-renderer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libinterp/corefcn/ft-text-renderer.h Sat Feb 06 08:15:53 2016 -0500 @@ -0,0 +1,31 @@ +/* + +Copyright (C) 2016 John W. Eaton +Copyright (C) 2009-2015 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 (octave_ft_text_renderer_h) +#define octave_ft_text_renderer_h 1 + +class base_text_renderer; + +extern base_text_renderer *make_ft_text_renderer (void); + +#endif diff -r f5e05c11c343 -r 67d2965af0b5 libinterp/corefcn/gl-render.cc --- a/libinterp/corefcn/gl-render.cc Fri Feb 05 14:53:10 2016 -0500 +++ b/libinterp/corefcn/gl-render.cc Sat Feb 06 08:15:53 2016 -0500 @@ -40,8 +40,7 @@ #include "errwarn.h" #include "gl-render.h" #include "oct-opengl.h" -#include "txt-eng.h" -#include "txt-eng-ft.h" +#include "text-renderer.h" #define LIGHT_MODE GL_FRONT_AND_BACK @@ -2897,20 +2896,17 @@ opengl_renderer::set_color (const Matrix& c) { glColor3dv (c.data ()); -#if HAVE_FREETYPE - text_renderer.set_color (c); -#endif + + txt_renderer.set_color (c); } void opengl_renderer::set_font (const base_properties& props) { -#if HAVE_FREETYPE - text_renderer.set_font (props.get ("fontname").string_value (), - props.get ("fontweight").string_value (), - props.get ("fontangle").string_value (), - props.get ("fontsize_points").double_value ()); -#endif + txt_renderer.set_font (props.get ("fontname").string_value (), + props.get ("fontweight").string_value (), + props.get ("fontangle").string_value (), + props.get ("fontsize_points").double_value ()); } void @@ -3238,22 +3234,18 @@ Matrix& bbox, int halign, int valign, double rotation) { -#if HAVE_FREETYPE - text_renderer.text_to_pixels (txt, pixels, bbox, - halign, valign, rotation, interpreter); -#endif + txt_renderer.text_to_pixels (txt, pixels, bbox, halign, valign, + rotation, interpreter); } void opengl_renderer::text_to_strlist (const std::string& txt, - std::list& lst, + std::list& lst, Matrix& bbox, int halign, int valign, double rotation) { -#if HAVE_FREETYPE - text_renderer.text_to_strlist (txt, lst, bbox, - halign, valign, rotation, interpreter); -#endif + txt_renderer.text_to_strlist (txt, lst, bbox, halign, valign, + rotation, interpreter); } Matrix @@ -3261,32 +3253,31 @@ double x, double y, double z, int halign, int valign, double rotation) { -#if HAVE_FREETYPE + Matrix bbox (1, 4, 0.0); + if (txt.empty ()) - return Matrix (1, 4, 0.0); - - uint8NDArray pixels; - Matrix bbox; - text_to_pixels (txt, pixels, bbox, halign, valign, rotation); - - bool blend = glIsEnabled (GL_BLEND); - - glEnable (GL_BLEND); - glEnable (GL_ALPHA_TEST); - glRasterPos3d (x, y, z); - glBitmap(0, 0, 0, 0, bbox(0), bbox(1), 0); - glDrawPixels (bbox(2), bbox(3), - GL_RGBA, GL_UNSIGNED_BYTE, pixels.data ()); - glDisable (GL_ALPHA_TEST); - if (! blend) - glDisable (GL_BLEND); + return bbox; + + if (txt_renderer.ok ()) + { + uint8NDArray pixels; + text_to_pixels (txt, pixels, bbox, halign, valign, rotation); + + bool blend = glIsEnabled (GL_BLEND); + + glEnable (GL_BLEND); + glEnable (GL_ALPHA_TEST); + glRasterPos3d (x, y, z); + glBitmap(0, 0, 0, 0, bbox(0), bbox(1), 0); + glDrawPixels (bbox(2), bbox(3), + GL_RGBA, GL_UNSIGNED_BYTE, pixels.data ()); + glDisable (GL_ALPHA_TEST); + + if (! blend) + glDisable (GL_BLEND); + } return bbox; -#else - warn_disabled_feature ("opengl_renderer::render_text", - "rendering text (FreeType)"); - return Matrix (1, 4, 0.0); -#endif } #endif diff -r f5e05c11c343 -r 67d2965af0b5 libinterp/corefcn/gl-render.h --- a/libinterp/corefcn/gl-render.h Fri Feb 05 14:53:10 2016 -0500 +++ b/libinterp/corefcn/gl-render.h Sat Feb 06 08:15:53 2016 -0500 @@ -25,7 +25,7 @@ #include "graphics.h" #include "oct-opengl.h" -#include "txt-eng-ft.h" +#include "text-renderer.h" #if defined (HAVE_OPENGL) @@ -37,10 +37,7 @@ opengl_renderer (void) : toolkit (), xform (), xmin (), xmax (), ymin (), ymax (), zmin (), zmax (), xZ1 (), xZ2 (), marker_id (), filled_marker_id (), - camera_pos (), camera_dir (), interpreter ("none") -#if HAVE_FREETYPE - , text_renderer () -#endif + camera_pos (), camera_dir (), interpreter ("none"), txt_renderer () { } virtual ~opengl_renderer (void) { } @@ -105,7 +102,7 @@ double rotation = 0.0); virtual void text_to_strlist (const std::string& txt, - std::list& lst, + std::list& lst, Matrix& bbox, int halign = 0, int valign = 0, double rotation = 0.0); @@ -195,10 +192,7 @@ // interpreter to be used by text_to_pixels caseless_str interpreter; -#if HAVE_FREETYPE - // FreeType render, used for text rendering - ft_render text_renderer; -#endif + text_renderer txt_renderer; private: class patch_tesselator; diff -r f5e05c11c343 -r 67d2965af0b5 libinterp/corefcn/gl2ps-print.cc --- a/libinterp/corefcn/gl2ps-print.cc Fri Feb 05 14:53:10 2016 -0500 +++ b/libinterp/corefcn/gl2ps-print.cc Sat Feb 06 08:15:53 2016 -0500 @@ -41,7 +41,7 @@ #include "gl-render.h" #include "oct-opengl.h" #include "sysdep.h" -#include "txt-eng-ft.h" +#include "text-renderer.h" class OCTINTERP_API @@ -123,11 +123,11 @@ private: - // Use xform to compute the coordinates of the ft_string list + // Use xform to compute the coordinates of the string list // that have been parsed by freetype void fix_strlist_position (double x, double y, double z, Matrix box, double rotation, - std::list& lst); + std::list& lst); int alignment_to_mode (int ha, int va) const; FILE *fp; @@ -302,9 +302,9 @@ void gl2ps_renderer::fix_strlist_position (double x, double y, double z, Matrix box, double rotation, - std::list& lst) + std::list& lst) { - for (std::list::iterator p = lst.begin (); + for (std::list::iterator p = lst.begin (); p != lst.end (); p++) { // Get pixel coordinates @@ -552,7 +552,7 @@ // string using freetype Matrix bbox; std::string str = txt; - std::list lst; + std::list lst; text_to_strlist (str, lst, bbox, ha, va, rotation); @@ -565,7 +565,7 @@ int sz = fontsize; if (! lst.empty () && term.find ("tex") == std::string::npos) { - ft_render::ft_string s = lst.front (); + text_renderer::string s = lst.front (); name = select_font (s.get_name (), s.get_weight () == "bold", s.get_angle () == "italic"); set_color (s.get_color ()); @@ -591,7 +591,7 @@ // Translate and rotate coordinates in order to use bottom-left alignment fix_strlist_position (x, y, z, bbox, rotation, lst); - for (std::list::iterator p = lst.begin (); + for (std::list::iterator p = lst.begin (); p != lst.end (); p++) { fontname = select_font ((*p).get_name (), diff -r f5e05c11c343 -r 67d2965af0b5 libinterp/corefcn/graphics.cc --- a/libinterp/corefcn/graphics.cc Fri Feb 05 14:53:10 2016 -0500 +++ b/libinterp/corefcn/graphics.cc Sat Feb 06 08:15:53 2016 -0500 @@ -54,8 +54,8 @@ #include "ov-fcn-handle.h" #include "pager.h" #include "parse.h" +#include "text-renderer.h" #include "toplev.h" -#include "txt-eng-ft.h" #include "unwind-prot.h" #include "utils.h" #include "octave-default-image.h" @@ -6167,14 +6167,10 @@ void axes::properties::update_font (void) { -#ifdef HAVE_FREETYPE -# ifdef HAVE_FONTCONFIG - text_renderer.set_font (get ("fontname").string_value (), - get ("fontweight").string_value (), - get ("fontangle").string_value (), - get ("fontsize_points").double_value ()); -#endif -#endif + txt_renderer.set_font (get ("fontname").string_value (), + get ("fontweight").string_value (), + get ("fontangle").string_value (), + get ("fontsize_points").double_value ()); } // The INTERNAL flag defines whether position or outerposition is used. @@ -6975,18 +6971,24 @@ std::string label (ticklabels(i)); label.erase (0, label.find_first_not_of (" ")); label = label.substr (0, label.find_last_not_of (" ")+1); -#ifdef HAVE_FREETYPE - ext = text_renderer.get_extent (label, 0.0, - get_ticklabelinterpreter ()); - wmax = std::max (wmax, ext(0)); - hmax = std::max (hmax, ext(1)); -#else - // FIXME: find a better approximation - double fsize = get ("fontsize").double_value (); - int len = label.length (); - wmax = std::max (wmax, 0.5*fsize*len); - hmax = fsize; -#endif + + if (txt_renderer.ok ()) + { + ext = txt_renderer.get_extent (label, 0.0, + get_ticklabelinterpreter ()); + + wmax = std::max (wmax, ext(0)); + hmax = std::max (hmax, ext(1)); + } + else + { + // FIXME: find a better approximation + double fsize = get ("fontsize").double_value (); + int len = label.length (); + + wmax = std::max (wmax, 0.5*fsize*len); + hmax = fsize; + } } } @@ -7940,22 +7942,17 @@ void text::properties::update_font (void) { -#ifdef HAVE_FREETYPE -# ifdef HAVE_FONTCONFIG - renderer.set_font (get ("fontname").string_value (), - get ("fontweight").string_value (), - get ("fontangle").string_value (), - get ("fontsize_points").double_value ()); -#endif - renderer.set_color (get_color_rgb ()); -#endif + txt_renderer.set_font (get ("fontname").string_value (), + get ("fontweight").string_value (), + get ("fontangle").string_value (), + get ("fontsize_points").double_value ()); + + txt_renderer.set_color (get_color_rgb ()); } void text::properties::update_text_extent (void) { -#ifdef HAVE_FREETYPE - int halign = 0; int valign = 0; @@ -7981,17 +7978,15 @@ string_vector sv = string_prop.string_vector_value (); - renderer.text_to_pixels (sv.join ("\n"), pixels, bbox, - halign, valign, get_rotation (), - get_interpreter ()); + txt_renderer.text_to_pixels (sv.join ("\n"), pixels, bbox, + halign, valign, get_rotation (), + get_interpreter ()); // The bbox is relative to the text's position. We'll leave it that // way, because get_position does not return valid results when the // text is first constructed. Conversion to proper coordinates is // performed in get_extent. set_extent (bbox); -#endif - if (autopos_tag_is ("xlabel") || autopos_tag_is ("ylabel") || autopos_tag_is ("zlabel") || autopos_tag_is ("title")) update_autopos ("sync"); @@ -8673,23 +8668,20 @@ void uicontrol::properties::update_text_extent (void) { -#ifdef HAVE_FREETYPE - text_element *elt; - ft_render text_renderer; + text_renderer txt_renderer; Matrix box; // FIXME: parsed content should be cached for efficiency // FIXME: support multiline text elt = text_parser::parse (get_string_string (), "none"); -#ifdef HAVE_FONTCONFIG - text_renderer.set_font (get_fontname (), - get_fontweight (), - get_fontangle (), - get_fontsize ()); -#endif - box = text_renderer.get_extent (elt, 0); + + txt_renderer.set_font (get_fontname (), get_fontweight (), + get_fontangle (), get_fontsize ()); + + box = txt_renderer.get_extent (elt, 0); + delete elt; Matrix ext (1, 4); @@ -8701,8 +8693,6 @@ ext(3) = box(1); set_extent (ext); - -#endif } void diff -r f5e05c11c343 -r 67d2965af0b5 libinterp/corefcn/graphics.in.h --- a/libinterp/corefcn/graphics.in.h Fri Feb 05 14:53:10 2016 -0500 +++ b/libinterp/corefcn/graphics.in.h Sat Feb 06 08:15:53 2016 -0500 @@ -44,7 +44,7 @@ #include "oct-mutex.h" #include "oct-refcount.h" #include "ov.h" -#include "txt-eng-ft.h" +#include "text-renderer.h" // FIXME: maybe this should be a configure option? // Matlab defaults to "Helvetica", but that causes problems for many @@ -3792,10 +3792,8 @@ bool x2Dtop, y2Dright, layer2Dtop, is2D; bool xySym, xyzSym, zSign, nearhoriz; -#if HAVE_FREETYPE - // FreeType renderer, used for calculation of text (tick labels) size - ft_render text_renderer; -#endif + // Text renderer, used for calculation of text (tick labels) size + text_renderer txt_renderer; void set_text_child (handle_property& h, const std::string& who, const octave_value& v); @@ -4511,10 +4509,9 @@ Matrix get_data_position (void) const; Matrix get_extent_matrix (void) const; const uint8NDArray& get_pixels (void) const { return pixels; } -#if HAVE_FREETYPE - // FreeType renderer, used for calculation of text size - ft_render renderer; -#endif + + // Text renderer, used for calculation of text size + text_renderer txt_renderer; protected: void init (void) diff -r f5e05c11c343 -r 67d2965af0b5 libinterp/corefcn/module.mk --- a/libinterp/corefcn/module.mk Fri Feb 05 14:53:10 2016 -0500 +++ b/libinterp/corefcn/module.mk Sat Feb 06 08:15:53 2016 -0500 @@ -21,6 +21,7 @@ libinterp/corefcn/pt-jit.h COREFCN_INC = \ + libinterp/corefcn/base-text-renderer.h \ libinterp/corefcn/Cell.h \ libinterp/corefcn/c-file-ptr-stream.h \ libinterp/corefcn/cdisplay.h \ @@ -37,6 +38,7 @@ libinterp/corefcn/errwarn.h \ libinterp/corefcn/event-queue.h \ libinterp/corefcn/file-io.h \ + libinterp/corefcn/ft-text-renderer.h \ libinterp/corefcn/gl-render.h \ libinterp/corefcn/gl2ps-print.h \ libinterp/corefcn/gripes.h \ @@ -85,8 +87,8 @@ libinterp/corefcn/sparse-xpow.h \ libinterp/corefcn/symtab.h \ libinterp/corefcn/sysdep.h \ + libinterp/corefcn/text-renderer.h \ libinterp/corefcn/toplev.h \ - libinterp/corefcn/txt-eng-ft.h \ libinterp/corefcn/txt-eng.h \ libinterp/corefcn/utils.h \ libinterp/corefcn/variables.h \ @@ -155,6 +157,7 @@ libinterp/corefcn/file-io.cc \ libinterp/corefcn/filter.cc \ libinterp/corefcn/find.cc \ + libinterp/corefcn/ft-text-renderer.cc \ libinterp/corefcn/gammainc.cc \ libinterp/corefcn/gcd.cc \ libinterp/corefcn/getgrent.cc \ @@ -236,10 +239,10 @@ libinterp/corefcn/syscalls.cc \ libinterp/corefcn/sysdep.cc \ libinterp/corefcn/time.cc \ + libinterp/corefcn/text-renderer.cc \ libinterp/corefcn/toplev.cc \ libinterp/corefcn/tril.cc \ libinterp/corefcn/tsearch.cc \ - libinterp/corefcn/txt-eng-ft.cc \ libinterp/corefcn/txt-eng.cc \ libinterp/corefcn/typecast.cc \ libinterp/corefcn/urlwrite.cc \ diff -r f5e05c11c343 -r 67d2965af0b5 libinterp/corefcn/text-renderer.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libinterp/corefcn/text-renderer.cc Sat Feb 06 08:15:53 2016 -0500 @@ -0,0 +1,140 @@ +/* + +Copyright (C) 2016 John W. Eaton +Copyright (C) 2009-2015 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 + +#include "base-text-renderer.h" +#include "errwarn.h" +#include "ft-text-renderer.h" +#include "text-renderer.h" + +static base_text_renderer * +make_text_renderer (void) +{ + // Allow the possibility of choosing different text rendering + // implementations. + + return make_ft_text_renderer (); +} + +text_renderer::text_renderer (void) + : rep (make_text_renderer ()) +{ } + +text_renderer::~text_renderer (void) +{ + delete rep; +} + +bool +text_renderer::ok (void) const +{ + static bool warned = false; + + if (! rep) + { + if (! warned) + { + warn_disabled_feature ("opengl_renderer::render_text", + "rendering text (FreeType)"); + + warned = true; + } + } + + return rep != 0; +} + +Matrix +text_renderer::get_extent (text_element *elt, double rotation) +{ + static Matrix empty_extent (1, 4, 0.0); + + return ok () ? rep->get_extent (elt, rotation) : empty_extent; +} + +Matrix +text_renderer::get_extent (const std::string& txt, double rotation, + const caseless_str& interpreter) +{ + static Matrix empty_extent (1, 4, 0.0); + + return ok () ? rep->get_extent (txt, rotation, interpreter) : empty_extent; +} + +void +text_renderer::set_font (const std::string& name, const std::string& weight, + const std::string& angle, double size) +{ + if (ok ()) + rep->set_font (name, weight, angle, size); +} + +void +text_renderer::set_color (const Matrix& c) +{ + if (ok ()) + rep->set_color (c); +} + +void +text_renderer::text_to_pixels (const std::string& txt, + uint8NDArray& pxls, Matrix& bbox, + int halign, int valign, double rotation, + const caseless_str& interpreter, + bool handle_rotation) +{ + static Matrix empty_bbox (1, 4, 0.0); + static uint8NDArray empty_pxls; + + if (ok ()) + rep->text_to_pixels (txt, pxls, bbox, halign, valign, rotation, + interpreter, handle_rotation); + else + { + bbox = empty_bbox; + pxls = empty_pxls; + } +} + +void +text_renderer::text_to_strlist (const std::string& txt, + std::list& lst, + Matrix& bbox, int halign, int valign, + double rotation, + const caseless_str& interpreter) +{ + static Matrix empty_bbox (1, 4, 0.0); + static std::list empty_lst; + + if (ok ()) + rep->text_to_strlist (txt, lst, bbox, halign, valign, rotation, + interpreter); + else + { + bbox = empty_bbox; + lst = empty_lst; + } +} diff -r f5e05c11c343 -r 67d2965af0b5 libinterp/corefcn/text-renderer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libinterp/corefcn/text-renderer.h Sat Feb 06 08:15:53 2016 -0500 @@ -0,0 +1,210 @@ +/* + +Copyright (C) 2016 John W. Eaton +Copyright (C) 2009-2015 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 (octave_text_renderer_h) +#define octave_text_renderer_h 1 + +#include +#include + +#include "caseless-str.h" +#include "dMatrix.h" +#include "uint8NDArray.h" + +#include "txt-eng.h" + +class base_text_renderer; + +class +OCTINTERP_API +text_renderer +{ +public: + + text_renderer (void); + + ~text_renderer (void); + + bool ok (void) const; + + Matrix get_extent (text_element *elt, double rotation = 0.0); + + Matrix get_extent (const std::string& txt, double rotation = 0.0, + const caseless_str& interpreter = "tex"); + + void set_font (const std::string& name, const std::string& weight, + const std::string& angle, double size); + + void set_color (const Matrix& c); + + void text_to_pixels (const std::string& txt, + uint8NDArray& pxls, Matrix& bbox, + int halign, int valign, double rotation = 0.0, + const caseless_str& interpreter = "tex", + bool handle_rotation = true); + + class font + { + public: + + font (void) + : name (), weight (), angle (), size (0) + { } + + font (const std::string& nm, const std::string& wt, + const std::string& ang, double sz) + : name (nm), weight (wt), angle (ang), size (sz) + { } + + font (const font& ft) + : name (ft.name), weight (ft.weight), angle (ft.angle), + size (ft.size) + { } + + ~font (void) { } + + font& operator = (const font& ft) + { + if (&ft != this) + { + name = ft.name; + weight = ft.weight; + angle = ft.angle; + size = ft.size; + } + + return *this; + } + + std::string get_name (void) const { return name; } + + std::string get_weight (void) const { return weight; } + + std::string get_angle (void) const { return angle; } + + double get_size (void) const { return size; } + + protected: + + std::string name; + std::string weight; + std::string angle; + double size; + }; + + // Container for substrings after parsing. + + class string + { + public: + + string (const std::string& s, font& f, const double x0, const double y0) + : str (s), fnt (f), x (x0), y (y0), z (0.0), code (0), + color (Matrix (1,3,0.0)) + { } + + string (const string& s) + : str (s.str), fnt (s.fnt), x (s.x), y (s.y), code (s.code), + color (s.color) + { } + + ~string (void) { } + + string& operator = (const string& s) + { + if (&s != this) + { + str = s.str; + fnt = s.fnt; + x = s.x; + y = s.y; + code = s.code; + color = s.color; + } + + return *this; + } + + void set_string (const std::string& s) { str = s; } + + std::string get_string (void) const { return str; } + + std::string get_name (void) const { return fnt.get_name (); } + + std::string get_weight (void) const { return fnt.get_weight (); } + + std::string get_angle (void) const { return fnt.get_angle (); } + + double get_size (void) const { return fnt.get_size (); } + + void set_x (const double x0) { x = x0; } + + double get_x (void) const { return x; } + + void set_y (const double y0) { y = y0; } + + double get_y (void) const { return y; } + + void set_z (const double z0) { z = z0; } + + double get_z (void) const { return z; } + + void set_code (const uint32_t c) { code = c; } + + uint32_t get_code (void) const { return code; } + + void set_color (const uint8NDArray& c) + { + color(0) = static_cast (c(0)) / 255; + color(1) = static_cast (c(1)) / 255; + color(2) = static_cast (c(2)) / 255; + } + + Matrix get_color (void) const { return color; } + + private: + + std::string str; + font fnt; + double x, y, z; + uint32_t code; + Matrix color; + }; + + void text_to_strlist (const std::string& txt, + std::list& lst, Matrix& box, + int halign, int valign, double rotation = 0.0, + const caseless_str& interpreter = "tex"); + +private: + + base_text_renderer *rep; + + // No copying! + + text_renderer (const text_renderer&); + + text_renderer& operator = (const text_renderer&); +}; + +#endif diff -r f5e05c11c343 -r 67d2965af0b5 libinterp/corefcn/txt-eng-ft.cc --- a/libinterp/corefcn/txt-eng-ft.cc Fri Feb 05 14:53:10 2016 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1127 +0,0 @@ -/* - -Copyright (C) 2009-2015 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 defined (HAVE_FREETYPE) - -#if defined (HAVE_FONTCONFIG) -# include -#endif - -#include -#include -#include -#include -#include - -#include "singleton-cleanup.h" - -#include "error.h" -#include "pr-output.h" -#include "txt-eng-ft.h" - -// FIXME: maybe issue at most one warning per glyph/font/size/weight -// combination. - -static void -warn_missing_glyph (FT_ULong c) -{ - warning_with_id ("Octave:missing-glyph", - "ft_render: skipping missing glyph for character '%x'", c); -} - -static void -warn_glyph_render (FT_ULong c) -{ - warning_with_id ("Octave:glyph-render", - "ft_render: unable to render glyph for character '%x'", c); -} - -#ifdef _MSC_VER -// This is just a trick to avoid multiple symbol definitions. -// PermMatrix.h contains a dllexport'ed Array -// that will cause MSVC not to generate a new instantiation and -// use the imported one instead. -#include "PermMatrix.h" -#endif - -// Forward declaration -static void ft_face_destroyed (void* object); - -class -ft_manager -{ -public: - static bool instance_ok (void) - { - bool retval = true; - - if (! instance) - { - instance = new ft_manager (); - - if (instance) - singleton_cleanup_list::add (cleanup_instance); - } - - if (! instance) - error ("unable to create ft_manager!"); - - return retval; - } - - static void cleanup_instance (void) { delete instance; instance = 0; } - - 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); - } - - static void font_destroyed (FT_Face face) - { - if (instance_ok ()) - instance->do_font_destroyed (face); - } - -private: - - static ft_manager *instance; - - typedef std::pair ft_key; - typedef std::map ft_cache; - - // Cache the fonts loaded by FreeType. This cache only contains - // weak references to the fonts, strong references are only present - // in class ft_render. - ft_cache cache; - -private: - - // No copying! - - ft_manager (const ft_manager&); - - ft_manager& operator = (const ft_manager&); - - ft_manager (void) - : library (), freetype_initialized (false), fontconfig_initialized (false) - { - if (FT_Init_FreeType (&library)) - error ("unable to initialize FreeType library"); - else - freetype_initialized = true; - -#if defined (HAVE_FONTCONFIG) - if (! FcInit ()) - error ("unable to initialize fontconfig library"); - else - fontconfig_initialized = true; -#endif - } - - ~ft_manager (void) - { - if (freetype_initialized) - FT_Done_FreeType (library); - -#if defined (HAVE_FONTCONFIG) - // FIXME: Skip the call to FcFini because it can trigger the assertion - // - // octave: fccache.c:507: FcCacheFini: Assertion 'fcCacheChains[i] == ((void *)0)' failed. - // - // if (fontconfig_initialized) - // FcFini (); -#endif - } - - - FT_Face do_get_font (const std::string& name, const std::string& weight, - const std::string& angle, double size) - { - FT_Face retval = 0; - -#if HAVE_FT_REFERENCE_FACE - // Look first into the font cache, then use fontconfig. If the font - // is present in the cache, simply add a reference and return it. - - ft_key key (name + ":" + weight + ":" + angle, size); - ft_cache::const_iterator it = cache.find (key); - - if (it != cache.end ()) - { - FT_Reference_Face (it->second); - return it->second; - } -#endif - - std::string file; - -#if defined (HAVE_FONTCONFIG) - if (fontconfig_initialized) - { - 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); - - // FIXME: originally, this test also required that - // res != FcResultNoMatch. Is that really needed? - if (match) - { - unsigned char *tmp; - - FcPatternGetString (match, FC_FILE, 0, &tmp); - file = reinterpret_cast (tmp); - } - else - ::warning ("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 (! file.empty ()) - { - if (FT_New_Face (library, file.c_str (), 0, &retval)) - ::warning ("ft_manager: unable to load font: %s", file.c_str ()); -#if HAVE_FT_REFERENCE_FACE - else - { - // Install a finalizer to notify ft_manager that the font is - // being destroyed. The class ft_manager only keeps weak - // references to font objects. - - retval->generic.data = new ft_key (key); - retval->generic.finalizer = ft_face_destroyed; - - // Insert loaded font into the cache. - - cache[key] = retval; - } -#endif - } - - return retval; - } - - void do_font_destroyed (FT_Face face) - { - if (face->generic.data) - { - ft_key* pkey = reinterpret_cast (face->generic.data); - - cache.erase (*pkey); - delete pkey; - face->generic.data = 0; - } - } - -private: - FT_Library library; - bool freetype_initialized; - bool fontconfig_initialized; -}; - -ft_manager* ft_manager::instance = 0; - -static void -ft_face_destroyed (void* object) -{ ft_manager::font_destroyed (reinterpret_cast (object)); } - -// --------------------------------------------------------------------------- - -ft_render::ft_render (void) - : text_processor (), font (), bbox (1, 4, 0.0), halign (0), xoffset (0), - line_yoffset (0), yoffset (0), mode (MODE_BBOX), - color (dim_vector (1, 3), 0) -{ -} - -ft_render::~ft_render (void) -{ -} - -void -ft_render::set_font (const std::string& name, const std::string& weight, - const std::string& angle, double size) -{ - // FIXME: take "fontunits" into account - - font = ft_font (name, weight, angle, size, 0); -} - -void -ft_render::push_new_line (void) -{ - switch (mode) - { - case MODE_BBOX: - { - // Create a new bbox entry based on the current font. - - FT_Face face = font.get_face (); - - if (face) - { - int asc = face->size->metrics.ascender >> 6; - int desc = face->size->metrics.descender >> 6; - int h = face->size->metrics.height >> 6; - - Matrix bb (1, 5, 0.0); - - bb(1) = desc; - bb(3) = asc - desc; - bb(4) = h; - - line_bbox.push_back (bb); - - xoffset = yoffset = 0; - } - } - break; - - case MODE_RENDER: - { - // Move to the next line bbox, adjust xoffset based on alignment - // and yoffset based on the old and new line bbox. - - Matrix old_bbox = line_bbox.front (); - line_bbox.pop_front (); - Matrix new_bbox = line_bbox.front (); - - xoffset = line_xoffset = compute_line_xoffset (new_bbox); - line_yoffset += (old_bbox(1) - (new_bbox(1) + new_bbox(3))); - yoffset = 0; - } - break; - } -} - -int -ft_render::compute_line_xoffset (const Matrix& lb) const -{ - if (! bbox.is_empty ()) - { - switch (halign) - { - case 0: - return 0; - case 1: - return (bbox(2) - lb(2)) / 2; - case 2: - return (bbox(2) - lb(2)); - } - } - - return 0; -} - -void -ft_render::compute_bbox (void) -{ - // Stack the various line bbox together and compute the final - // bounding box for the entire text string. - - bbox = Matrix (); - - switch (line_bbox.size ()) - { - case 0: - break; - case 1: - bbox = line_bbox.front ().extract (0, 0, 0, 3); - break; - default: - for (std::list::const_iterator it = line_bbox.begin (); - it != line_bbox.end (); ++it) - { - if (bbox.is_empty ()) - bbox = it->extract (0, 0, 0, 3); - else - { - bbox(1) -= (*it)(3); - bbox(3) += (*it)(3); - bbox(2) = xmax (bbox(2), (*it)(2)); - } - } - break; - } -} - -void -ft_render::update_line_bbox (void) -{ - // Called after a font change, when in MODE_BBOX mode, to update the - // current line bbox with the new font metrics. This also includes the - // current yoffset, that is the offset of the current glyph's baseline - // the line's baseline. - - if (mode == MODE_BBOX) - { - int asc = font.get_face ()->size->metrics.ascender >> 6; - int desc = font.get_face ()->size->metrics.descender >> 6; - - Matrix& bb = line_bbox.back (); - - if ((yoffset + desc) < bb(1)) - { - // The new font goes below the bottom of the current bbox. - - int delta = bb(1) - (yoffset + desc); - - bb(1) -= delta; - bb(3) += delta; - } - - if ((yoffset + asc) > (bb(1) + bb(3))) - { - // The new font goes above the top of the current bbox. - - int delta = (yoffset + asc) - (bb(1) + bb(3)); - - bb(3) += delta; - } - } -} - -void -ft_render::set_mode (int m) -{ - mode = m; - - switch (mode) - { - case MODE_BBOX: - xoffset = line_yoffset = yoffset = 0; - bbox = Matrix (1, 4, 0.0); - line_bbox.clear (); - push_new_line (); - break; - case MODE_RENDER: - if (bbox.numel () != 4) - { - ::warning ("ft_render: invalid bounding box, cannot render"); - - xoffset = line_yoffset = yoffset = 0; - pixels = uint8NDArray (); - } - else - { - pixels = uint8NDArray (dim_vector (4, bbox(2), bbox(3)), - static_cast (0)); - xoffset = compute_line_xoffset (line_bbox.front ()); - line_yoffset = -bbox(1)-1; - yoffset = 0; - } - break; - default: - error ("ft_render: invalid mode '%d'", mode); - break; - } -} - -FT_UInt -ft_render::process_character (FT_ULong code, FT_UInt previous) -{ - FT_Face face = font.get_face (); - FT_UInt glyph_index = 0; - - if (face) - { - glyph_index = FT_Get_Char_Index (face, code); - - if (code != '\n' - && (! glyph_index - || FT_Load_Glyph (face, glyph_index, FT_LOAD_DEFAULT))) - { - glyph_index = 0; - warn_missing_glyph (code); - } - else - { - switch (mode) - { - case MODE_RENDER: - if (code == '\n') - { - glyph_index = FT_Get_Char_Index (face, ' '); - if (! glyph_index - || FT_Load_Glyph (face, glyph_index, FT_LOAD_DEFAULT)) - { - glyph_index = 0; - warn_missing_glyph (' '); - } - else - push_new_line (); - } - else if (FT_Render_Glyph (face->glyph, FT_RENDER_MODE_NORMAL)) - { - glyph_index = 0; - warn_glyph_render (code); - } - 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 = line_yoffset + yoffset + face->glyph->bitmap_top; - - // 'w' seems to have a negative -1 - // face->glyph->bitmap_left, this is so we don't - // index out of bound, and assumes we we allocated - // the right amount of horizontal space in the bbox. - if (x0 < 0) - x0 = 0; - - for (int r = 0; static_cast (r) < bitmap.rows; r++) - for (int c = 0; static_cast (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 ()) - { - //::warning ("ft_render: pixel out of bound (char=%d, (x,y)=(%d,%d), (w,h)=(%d,%d)", - // str[i], x0+c, y0-r, pixels.dim2 (), pixels.dim3 ()); - } - else if (pixels(3, x0+c, y0-r).value () == 0) - { - pixels(0, x0+c, y0-r) = color(0); - pixels(1, x0+c, y0-r) = color(1); - pixels(2, x0+c, y0-r) = color(2); - pixels(3, x0+c, y0-r) = pix; - } - } - - xoffset += (face->glyph->advance.x >> 6); - } - break; - - case MODE_BBOX: - if (code == '\n') - { - glyph_index = FT_Get_Char_Index (face, ' '); - if (! glyph_index - || FT_Load_Glyph (face, glyph_index, FT_LOAD_DEFAULT)) - { - glyph_index = 0; - warn_missing_glyph (' '); - } - else - push_new_line (); - } - else - { - Matrix& bb = line_bbox.back (); - - // If we have a previous glyph, use kerning information. - // This usually means moving a bit backward before adding - // the next glyph. That is, "delta.x" is usually < 0. - if (previous) - { - FT_Vector delta; - - FT_Get_Kerning (face, previous, glyph_index, - FT_KERNING_DEFAULT, &delta); - - xoffset += (delta.x >> 6); - } - - // Extend current X offset box by the width of the current - // glyph. Then extend the line bounding box if necessary. - - xoffset += (face->glyph->advance.x >> 6); - bb(2) = xmax (bb(2), xoffset); - } - break; - } - } - } - - return glyph_index; -} - -void -ft_render::visit (text_element_string& e) -{ - if (font.is_valid ()) - { - FT_UInt glyph_index, previous = 0; - - std::string str = e.string_value (); - size_t n = str.length (); - size_t curr = 0; - size_t idx = 0; - mbstate_t ps; - memset (&ps, 0, sizeof (ps)); // Initialize state to 0. - wchar_t wc; - - ft_string fs (str, font.get_angle (), font.get_weight (), - font.get_name (), font.get_size (), xoffset, yoffset); - - while (n > 0) - { - size_t r = gnulib::mbrtowc (&wc, str.data () + curr, n, &ps); - - if (r > 0 - && r != static_cast (-1) - && r != static_cast (-2)) - { - n -= r; - curr += r; - - if (wc == L'\n') - { - // Finish previous string in srtlist before processing - // the newline character - fs.set_y (line_yoffset + yoffset); - fs.set_color (color); - std::string s = str.substr (idx, curr - idx - 1); - if (! s.empty ()) - { - fs.set_string (s); - strlist.push_back (fs); - } - } - - glyph_index = process_character (wc, previous); - - if (wc == L'\n') - { - previous = 0; - // Start a new string in strlist - idx = curr; - fs = ft_string (str.substr (idx), font.get_angle (), - font.get_weight (), font.get_name (), - font.get_size (), line_xoffset, yoffset); - - } - else - previous = glyph_index; - } - else - { - if (r != 0) - ::warning ("ft_render: failed to decode string `%s' with " - "locale `%s'", str.c_str (), - std::setlocale (LC_CTYPE, 0)); - break; - } - } - if (! fs.get_string ().empty ()) - { - fs.set_y (line_yoffset + yoffset); - fs.set_color (color); - strlist.push_back (fs); - } - } -} - -void -ft_render::visit (text_element_list& e) -{ - // Save and restore (after processing the list) the current font and color. - - ft_font saved_font (font); - uint8NDArray saved_color (color); - - text_processor::visit (e); - - font = saved_font; - color = saved_color; -} - -void -ft_render::visit (text_element_subscript& e) -{ - ft_font saved_font (font); - int saved_line_yoffset = line_yoffset; - int saved_yoffset = yoffset; - - set_font (font.get_name (), font.get_weight (), font.get_angle (), - font.get_size () - 2); - - if (font.is_valid ()) - { - int h = font.get_face ()->size->metrics.height >> 6; - - // Shifting the baseline by 2/3 the font height seems to produce - // decent result. - yoffset -= (h * 2) / 3; - - if (mode == MODE_BBOX) - update_line_bbox (); - } - - text_processor::visit (e); - - font = saved_font; - // If line_yoffset changed, this means we moved to a new line; hence yoffset - // cannot be restored, because the saved value is not relevant anymore. - if (line_yoffset == saved_line_yoffset) - yoffset = saved_yoffset; -} - -void -ft_render::visit (text_element_superscript& e) -{ - ft_font saved_font (font); - int saved_line_yoffset = line_yoffset; - int saved_yoffset = yoffset; - - set_font (font.get_name (), font.get_weight (), font.get_angle (), - font.get_size () - 2); - - if (saved_font.is_valid ()) - { - int s_asc = saved_font.get_face ()->size->metrics.ascender >> 6; - - // Shifting the baseline by 2/3 base font ascender seems to produce - // decent result. - yoffset += (s_asc * 2) / 3; - - if (mode == MODE_BBOX) - update_line_bbox (); - } - - text_processor::visit (e); - - font = saved_font; - // If line_yoffset changed, this means we moved to a new line; hence yoffset - // cannot be restored, because the saved value is not relevant anymore. - if (line_yoffset == saved_line_yoffset) - yoffset = saved_yoffset; -} - -void -ft_render::visit (text_element_color& e) -{ - if (mode == MODE_RENDER) - set_color (e.get_color ()); -} - -void -ft_render::visit (text_element_fontsize& e) -{ - double sz = e.get_fontsize (); - - // FIXME: Matlab documentation says that the font size is expressed - // in the text object FontUnit. - - set_font (font.get_name (), font.get_weight (), font.get_angle (), sz); - - if (mode == MODE_BBOX) - update_line_bbox (); -} - -void -ft_render::visit (text_element_fontname& e) -{ - set_font (e.get_fontname (), font.get_weight (), font.get_angle (), - font.get_size ()); - - if (mode == MODE_BBOX) - update_line_bbox (); -} - -void -ft_render::visit (text_element_fontstyle& e) -{ - switch (e.get_fontstyle ()) - { - case text_element_fontstyle::normal: - set_font (font.get_name (), "normal", "normal", font.get_size ()); - break; - case text_element_fontstyle::bold: - set_font (font.get_name (), "bold", "normal", font.get_size ()); - break; - case text_element_fontstyle::italic: - set_font (font.get_name (), "normal", "italic", font.get_size ()); - break; - case text_element_fontstyle::oblique: - set_font (font.get_name (), "normal", "oblique", font.get_size ()); - break; - } - - if (mode == MODE_BBOX) - update_line_bbox (); -} - -void -ft_render::visit (text_element_symbol& e) -{ - uint32_t code = e.get_symbol_code (); - - ft_string fs (std::string ("-"), font.get_angle (), font.get_weight (), - font.get_name (), font.get_size (), xoffset, yoffset); - - if (code != text_element_symbol::invalid_code && font.is_valid ()) - { - process_character (code); - fs.set_code (code); - } - else if (font.is_valid ()) - ::warning ("ignoring unknown symbol: %d", e.get_symbol ()); - - if (fs.get_code ()) - { - fs.set_y (line_yoffset + yoffset); - fs.set_color (color); - strlist.push_back (fs); - } -} - -void -ft_render::visit (text_element_combined& e) -{ - int saved_xoffset = xoffset; - int max_xoffset = xoffset; - - for (text_element_combined::iterator it = e.begin (); it != e.end (); ++it) - { - xoffset = saved_xoffset; - (*it)->accept (*this); - max_xoffset = xmax (xoffset, max_xoffset); - } - - xoffset = max_xoffset; -} - -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) - { - color(0) = static_cast (c(0)*255); - color(1) = static_cast (c(1)*255); - color(2) = static_cast (c(2)*255); - } - else - ::warning ("ft_render::set_color: invalid color"); -} - -uint8NDArray -ft_render::render (text_element* elt, Matrix& box, int rotation) -{ - set_mode (MODE_BBOX); - elt->accept (*this); - compute_bbox (); - box = bbox; - - set_mode (MODE_RENDER); - // Clear the list of parsed strings - strlist.clear (); - - if (pixels.numel () > 0) - { - elt->accept (*this); - - switch (rotation) - { - case ROTATION_0: - break; - case ROTATION_90: - { - Array perm (dim_vector (3, 1)); - perm(0) = 0; - perm(1) = 2; - perm(2) = 1; - pixels = pixels.permute (perm); - - Array idx (dim_vector (3, 1)); - 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 (dim_vector (3, 1)); - idx(0) = idx_vector (':'); - idx(1) = idx_vector (pixels.dim2 ()-1, -1, -1); - idx(2)= idx_vector (pixels.dim3 ()-1, -1, -1); - pixels = uint8NDArray (pixels.index (idx)); - } - break; - case ROTATION_270: - { - Array perm (dim_vector (3, 1)); - perm(0) = 0; - perm(1) = 2; - perm(2) = 1; - pixels = pixels.permute (perm); - - Array idx (dim_vector (3, 1)); - idx(0) = idx_vector (':'); - idx(1) = idx_vector (':'); - idx(2) = idx_vector (pixels.dim3 ()-1, -1, -1); - pixels = uint8NDArray (pixels.index (idx)); - } - break; - } - } - - return pixels; -} - -// Note: -// x-extent accurately measures width of glyphs. -// y-extent is overly large because it is measured from baseline-to-baseline. -// Calling routines, such as ylabel, may need to account for this mismatch. - -Matrix -ft_render::get_extent (text_element *elt, double rotation) -{ - set_mode (MODE_BBOX); - elt->accept (*this); - compute_bbox (); - - Matrix extent (1, 2, 0.0); - - switch (rotation_to_mode (rotation)) - { - case ROTATION_0: - case ROTATION_180: - extent(0) = bbox(2); - extent(1) = bbox(3); - break; - case ROTATION_90: - case ROTATION_270: - extent(0) = bbox(3); - extent(1) = bbox(2); - } - - return extent; -} - -Matrix -ft_render::get_extent (const std::string& txt, double rotation, - const caseless_str& interpreter) -{ - text_element *elt = text_parser::parse (txt, interpreter); - Matrix extent = get_extent (elt, rotation); - delete elt; - - return extent; -} - -int -ft_render::rotation_to_mode (double rotation) const -{ - // Clip rotation to range [0, 360] - while (rotation < 0) - rotation += 360.0; - while (rotation > 360.0) - rotation -= 360.0; - - if (rotation == 0.0) - return ROTATION_0; - else if (rotation == 90.0) - return ROTATION_90; - else if (rotation == 180.0) - return ROTATION_180; - else if (rotation == 270.0) - return ROTATION_270; - else - return ROTATION_0; -} - -void -ft_render::text_to_pixels (const std::string& txt, - uint8NDArray& pixels_, Matrix& box, - int _halign, int valign, double rotation, - const caseless_str& interpreter, - bool handle_rotation) -{ - int rot_mode = rotation_to_mode (rotation); - - halign = _halign; - - text_element *elt = text_parser::parse (txt, interpreter); - pixels_ = render (elt, box, rot_mode); - delete elt; - - if (pixels_.is_empty ()) - return; // nothing to render - - switch (halign) - { - default: box(0) = 0; break; - case 1: box(0) = -box(2)/2; break; - case 2: box(0) = -box(2); break; - } - switch (valign) - { - default: box(1) = 0; break; - case 1: box(1) = -box(3)/2; break; - case 2: box(1) = -box(3); break; - case 3: break; - case 4: box(1) = -box(3)-box(1); break; - } - - if (handle_rotation) - switch (rot_mode) - { - case ROTATION_90: - std::swap (box(0), box(1)); - std::swap (box(2), box(3)); - box(0) = -box(0)-box(2); - break; - case ROTATION_180: - box(0) = -box(0)-box(2); - box(1) = -box(1)-box(3); - break; - case ROTATION_270: - std::swap (box(0), box(1)); - std::swap (box(2), box(3)); - box(1) = -box(1)-box(3); - break; - } -} - -ft_render::ft_font::ft_font (const ft_font& ft) - : name (ft.name), weight (ft.weight), angle (ft.angle), size (ft.size), - face (0) -{ -#if HAVE_FT_REFERENCE_FACE - FT_Face ft_face = ft.get_face (); - - if (ft_face && FT_Reference_Face (ft_face) == 0) - face = ft_face; -#endif -} - -ft_render::ft_font& -ft_render::ft_font::operator = (const ft_font& ft) -{ - if (&ft != this) - { - name = ft.name; - weight = ft.weight; - angle = ft.angle; - size = ft.size; - if (face) - { - FT_Done_Face (face); - face = 0; - } - -#if HAVE_FT_REFERENCE_FACE - FT_Face ft_face = ft.get_face (); - - if (ft_face && FT_Reference_Face (ft_face) == 0) - face = ft_face; -#endif - } - - return *this; -} - -FT_Face -ft_render::ft_font::get_face (void) const -{ - if (! face && ! name.empty ()) - { - face = ft_manager::get_font (name, weight, angle, size); - - if (face) - { - if (FT_Set_Char_Size (face, 0, size*64, 0, 0)) - ::warning ("ft_render: unable to set font size to %g", size); - } - else - ::warning ("ft_render: unable to load appropriate font"); - } - - return face; -} - -#endif diff -r f5e05c11c343 -r 67d2965af0b5 libinterp/corefcn/txt-eng-ft.h --- a/libinterp/corefcn/txt-eng-ft.h Fri Feb 05 14:53:10 2016 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,279 +0,0 @@ -/* - -Copyright (C) 2009-2015 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 - -#include -#include FT_FREETYPE_H - -#include -#include -#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 visit (text_element_list& e); - - void visit (text_element_subscript& e); - - void visit (text_element_superscript& e); - - void visit (text_element_color& e); - - void visit (text_element_fontsize& e); - - void visit (text_element_fontname& e); - - void visit (text_element_fontstyle& e); - - void visit (text_element_symbol& e); - - void visit (text_element_combined& 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); - - Matrix get_extent (text_element *elt, double rotation = 0.0); - Matrix get_extent (const std::string& txt, double rotation = 0.0, - const caseless_str& interpreter = "tex"); - - void set_font (const std::string& name, const std::string& weight, - const std::string& angle, double size); - - void set_color (Matrix c); - - void set_mode (int m); - - void text_to_pixels (const std::string& txt, - uint8NDArray& pixels_, Matrix& bbox, - int halign, int valign, double rotation, - const caseless_str& interpreter = "tex", - bool handle_rotation = true); - -private: - int rotation_to_mode (double rotation) const; - - // No copying! - - ft_render (const ft_render&); - - ft_render& operator = (const ft_render&); - - // Class to hold information about fonts and a strong - // reference to the font objects loaded by FreeType. - class ft_font - { - public: - ft_font (void) - : name (), weight (), angle (), size (0), face (0) { } - - ft_font (const std::string& nm, const std::string& wt, - const std::string& ang, double sz, FT_Face f = 0) - : name (nm), weight (wt), angle (ang), size (sz), face (f) { } - - ft_font (const ft_font& ft); - - ~ft_font (void) - { - if (face) - FT_Done_Face (face); - } - - ft_font& operator = (const ft_font& ft); - - bool is_valid (void) const { return get_face (); } - - std::string get_name (void) const { return name; } - - std::string get_weight (void) const { return weight; } - - std::string get_angle (void) const { return angle; } - - double get_size (void) const { return size; } - - FT_Face get_face (void) const; - - private: - std::string name; - std::string weight; - std::string angle; - double size; - mutable FT_Face face; - }; - - void push_new_line (void); - - void update_line_bbox (void); - - void compute_bbox (void); - - int compute_line_xoffset (const Matrix& lb) const; - - FT_UInt process_character (FT_ULong code, FT_UInt previous = 0); - -public: - // A class to store informations on substrings after parsing. - class ft_string : public ft_font - { - public: - ft_string (const std::string s, const std::string fontang, - const std::string fontwgt, const std::string nm, - const double fontsz, const double x0, const double y0) - : ft_font (nm, fontwgt, fontang, fontsz), - string(s), x(x0), y(y0), z(0.0), code(0), - color(Matrix (1,3,0.0)){ } - - void set_string (const std::string str) { string = str; } - - std::string get_string (void) const { return string; } - - void set_x (const double x0) { x = x0; } - - double get_x (void) const { return x; } - - void set_y (const double y0) { y = y0; } - - double get_y (void) const { return y; } - - void set_z (const double z0) { z = z0; } - - double get_z (void) const { return z; } - - void set_code (const uint32_t c) { code = c; } - - uint32_t get_code (void) const { return code; } - - void set_color (const uint8NDArray c) - { - color(0) = static_cast (c(0)) / 255; - color(1) = static_cast (c(1)) / 255; - color(2) = static_cast (c(2)) / 255; - } - - Matrix get_color (void) const { return color; } - - private: - std::string string; - double x, y, z; - uint32_t code; - Matrix color; - }; - - void text_to_strlist (const std::string& txt, - std::list& lst, Matrix& box, - int ha, int va, double rot, - const caseless_str& interp = "tex") - { - uint8NDArray pixels_; - // First run text_to_pixels which will also build the string list - text_to_pixels (txt, pixels_, box, ha, va, rot, interp, false); - - lst = strlist; - } - -private: - // The current font used by the renderer. - ft_font font; - - // Used to stored the bounding box corresponding to the rendered text. - // The bounding box has the form [x, y, w, h] where x and y represent the - // coordinates of the bottom left corner relative to the anchor point of - // the text (== start of text on the baseline). Due to font descent or - // multiple lines, the value y is usually negative. - Matrix bbox; - - // Used to stored the rendered text. It's a 3D matrix with size MxNx4 - // where M and N are the width and height of the bounding box. - uint8NDArray pixels; - - // Used to store the bounding box of each line. This is used to layout - // multiline text properly. - std::list line_bbox; - - // The current horizontal alignment. This is used to align multi-line text. - int halign; - - // The X offset for the next glyph. - int xoffset; - - // The Y offset of the baseline for the current line. - int line_yoffset; - - // The Y offset of the baseline for the next glyph. The offset is relative - // to line_yoffset. The total Y offset is computed with: - // line_yoffset + yoffset. - int yoffset; - - // The current mode of the rendering process (box computing or rendering). - int mode; - - // The base color of the rendered text. - uint8NDArray color; - - // A list of parsed strings to be used for printing. - std::list strlist; - - // The X offset of the baseline for the current line. - int line_xoffset; - -}; - -#endif - -#endif