Mercurial > octave
diff libinterp/corefcn/ft-text-renderer.cc @ 21209:67d2965af0b5
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.
author | John W. Eaton <jwe@octave.org> |
---|---|
date | Sat, 06 Feb 2016 08:15:53 -0500 |
parents | libinterp/corefcn/txt-eng-ft.cc@fcac5dbbf9ed |
children | 40de9f8f23a6 |
line wrap: on
line diff
--- /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 +<http://www.gnu.org/licenses/>. + +*/ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "base-text-renderer.h" + +#if defined (HAVE_FREETYPE) + +#include <ft2build.h> +#include FT_FREETYPE_H + +#if defined (HAVE_FONTCONFIG) +# include <fontconfig/fontconfig.h> +#endif + +#include <clocale> +#include <cwchar> +#include <iostream> +#include <map> +#include <utility> + +#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<octave_idx_type> +// 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<std::string, double> ft_key; + typedef std::map<ft_key, FT_Face> 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<const FcChar8*> + (name == "*" ? "sans" : name.c_str ()))); + + FcPatternAddInteger (pat, FC_WEIGHT, fc_weight); + FcPatternAddInteger (pat, FC_SLANT, fc_angle); + FcPatternAddDouble (pat, FC_PIXEL_SIZE, size); + + if (FcConfigSubstitute (0, pat, FcMatchPattern)) + { + FcResult res; + FcPattern *match; + + FcDefaultSubstitute (pat); + match = FcFontMatch (0, pat, &res); + + // 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<char*> (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<ft_key*> (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<FT_Face> (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<text_renderer::string>& 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<Matrix> 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<text_renderer::string> 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<Matrix>::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<uint8_t> (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<unsigned int> (r) < bitmap.rows; r++) + for (int c = 0; static_cast<unsigned int> (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<text_renderer::string>& 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<size_t> (-1) + && r != static_cast<size_t> (-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<uint8_t> (c(0)*255); + color(1) = static_cast<uint8_t> (c(1)*255); + color(2) = static_cast<uint8_t> (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<octave_idx_type> perm (dim_vector (3, 1)); + perm(0) = 0; + perm(1) = 2; + perm(2) = 1; + pixels = pixels.permute (perm); + + Array<idx_vector> 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_vector> 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<octave_idx_type> perm (dim_vector (3, 1)); + perm(0) = 0; + perm(1) = 2; + perm(2) = 1; + pixels = pixels.permute (perm); + + Array<idx_vector> 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 +}