Mercurial > octave
view libinterp/corefcn/ft-text-renderer.cc @ 30564:796f54d4ddbf stable
update Octave Project Developers copyright for the new year
In files that have the "Octave Project Developers" copyright notice,
update for 2021.
In all .txi and .texi files except gpl.txi and gpl.texi in the
doc/liboctave and doc/interpreter directories, change the copyright
to "Octave Project Developers", the same as used for other source
files. Update copyright notices for 2022 (not done since 2019). For
gpl.txi and gpl.texi, change the copyright notice to be "Free Software
Foundation, Inc." and leave the date at 2007 only because this file
only contains the text of the GPL, not anything created by the Octave
Project Developers.
Add Paul Thomas to contributors.in.
author | John W. Eaton <jwe@octave.org> |
---|---|
date | Tue, 28 Dec 2021 18:22:40 -0500 |
parents | a61e1a0f6024 |
children | e88a07dec498 |
line wrap: on
line source
//////////////////////////////////////////////////////////////////////// // // Copyright (C) 2009-2022 The Octave Project Developers // // See the file COPYRIGHT.md in the top-level directory of this // distribution or <https://octave.org/copyright/>. // // 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 // <https://www.gnu.org/licenses/>. // //////////////////////////////////////////////////////////////////////// #if defined (HAVE_CONFIG_H) # include "config.h" #endif #include "base-text-renderer.h" #include "ft-text-renderer.h" #if defined (HAVE_FREETYPE) #if defined (HAVE_PRAGMA_GCC_DIAGNOSTIC) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wold-style-cast" #endif #include <ft2build.h> #include FT_FREETYPE_H #include FT_GLYPH_H #if defined (HAVE_FONTCONFIG) # include <fontconfig/fontconfig.h> #endif #if defined (HAVE_PRAGMA_GCC_DIAGNOSTIC) # pragma GCC diagnostic pop #endif #include <clocale> #include <cwchar> #include <map> #include <utility> #include "singleton-cleanup.h" #include "unistr-wrappers.h" #include "defaults.h" #include "error.h" #include "file-ops.h" #include "oct-env.h" #include "pr-output.h" #include "sysdep.h" #include "text-renderer.h" namespace octave { // 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 '%lx'", c); } static void warn_glyph_render (FT_ULong c) { warning_with_id ("Octave:glyph-render", "text_renderer: unable to render glyph for character '%lx'", 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 { private: ft_manager (void) : m_library (), m_freetype_initialized (false), m_fontconfig_initialized (false) { if (FT_Init_FreeType (&m_library)) error ("unable to initialize FreeType library"); else m_freetype_initialized = true; #if defined (HAVE_FONTCONFIG) if (! FcInit ()) error ("unable to initialize fontconfig library"); else m_fontconfig_initialized = true; #endif } public: // No copying! ft_manager (const ft_manager&) = delete; ft_manager& operator = (const ft_manager&) = delete; private: ~ft_manager (void) { if (m_freetype_initialized) FT_Done_FreeType (m_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 (m_fontconfig_initialized) // FcFini (); #endif } public: static bool instance_ok (void) { bool retval = true; if (! m_instance) { m_instance = new ft_manager (); singleton_cleanup_list::add (cleanup_instance); } return retval; } static void cleanup_instance (void) { delete m_instance; m_instance = nullptr; } static FT_Face get_font (const std::string& name, const std::string& weight, const std::string& angle, double size, FT_ULong c = 0) { return (instance_ok () ? m_instance->do_get_font (name, weight, angle, size, c) : nullptr); } static octave_map get_system_fonts (void) { return (instance_ok () ? m_instance->do_get_system_fonts () : octave_map ()); } static void font_destroyed (FT_Face face) { if (instance_ok ()) m_instance->do_font_destroyed (face); } private: typedef std::pair<std::string, double> ft_key; typedef std::map<ft_key, FT_Face> ft_cache; static octave_map do_get_system_fonts (void) { static octave_map font_map; if (font_map.isempty ()) { #if defined (HAVE_FONTCONFIG) FcConfig *config = FcConfigGetCurrent(); FcPattern *pat = FcPatternCreate (); FcObjectSet *os = FcObjectSetBuild (FC_FAMILY, FC_SLANT, FC_WEIGHT, FC_CHARSET, nullptr); FcFontSet *fs = FcFontList (config, pat, os); if (fs->nfont > 0) { // Mark fonts that have at least all printable ASCII chars FcCharSet *minimal_charset = FcCharSetCreate (); for (int i = 32; i < 127; i++) FcCharSetAddChar (minimal_charset, static_cast<FcChar32> (i)); string_vector fields (4); fields(0) = "family"; fields(1) = "angle"; fields(2) = "weight"; fields(3) = "suitable"; dim_vector dv (1, fs->nfont); Cell families (dv); Cell angles (dv); Cell weights (dv); Cell suitable (dv); unsigned char *family; int val; for (int i = 0; fs && i < fs->nfont; i++) { FcPattern *font = fs->fonts[i]; if (FcPatternGetString (font, FC_FAMILY, 0, &family) == FcResultMatch) families(i) = std::string (reinterpret_cast<char *> (family)); else families(i) = "unknown"; if (FcPatternGetInteger (font, FC_SLANT, 0, &val) == FcResultMatch) angles(i) = (val == FC_SLANT_ITALIC || val == FC_SLANT_OBLIQUE) ? "italic" : "normal"; else angles(i) = "unknown"; if (FcPatternGetInteger (font, FC_WEIGHT, 0, &val) == FcResultMatch) weights(i) = (val == FC_WEIGHT_BOLD || val == FC_WEIGHT_DEMIBOLD) ? "bold" : "normal"; else weights(i) = "unknown"; FcCharSet *cset; if (FcPatternGetCharSet (font, FC_CHARSET, 0, &cset) == FcResultMatch) suitable(i) = (FcCharSetIsSubset (minimal_charset, cset) ? true : false); else suitable(i) = false; } font_map = octave_map (dv, fields); font_map.assign ("family", families); font_map.assign ("angle", angles); font_map.assign ("weight", weights); font_map.assign ("suitable", suitable); if (fs) FcFontSetDestroy (fs); } #endif } return font_map; } FT_Face do_get_font (const std::string& name, const std::string& weight, const std::string& angle, double size, FT_ULong search_code_point) { FT_Face retval = nullptr; #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 + ':' + std::to_string (search_code_point), size); ft_cache::const_iterator it = m_cache.find (key); if (it != m_cache.end ()) { FT_Reference_Face (it->second); return it->second; } #endif static std::string fonts_dir; if (fonts_dir.empty ()) { fonts_dir = sys::env::getenv ("OCTAVE_FONTS_DIR"); if (fonts_dir.empty ()) #if defined (SYSTEM_FREEFONT_DIR) fonts_dir = SYSTEM_FREEFONT_DIR; #else fonts_dir = config::oct_fonts_dir (); #endif } // Default font file std::string file; if (! fonts_dir.empty ()) { file = fonts_dir + sys::file_ops::dir_sep_str () + "FreeSans"; if (weight == "bold") file += "Bold"; if (angle == "italic" || angle == "oblique") file += "Oblique"; file += ".otf"; } #if defined (HAVE_FONTCONFIG) if ((search_code_point != 0 || name != "*") && m_fontconfig_initialized) { int fc_weight, fc_angle; if (weight == "bold") fc_weight = FC_WEIGHT_BOLD; 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.c_str ()))); FcPatternAddInteger (pat, FC_WEIGHT, fc_weight); FcPatternAddInteger (pat, FC_SLANT, fc_angle); FcPatternAddDouble (pat, FC_PIXEL_SIZE, size); if (search_code_point > 0) { FcCharSet *minimal_charset = FcCharSetCreate (); FcCharSetAddChar (minimal_charset, static_cast<FcChar32> (search_code_point)); FcPatternAddCharSet (pat, FC_CHARSET, minimal_charset); } if (FcConfigSubstitute (nullptr, pat, FcMatchPattern)) { FcResult res; FcPattern *match; FcDefaultSubstitute (pat); match = FcFontMatch (nullptr, 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, using default font", name.c_str (), weight.c_str (), angle.c_str (), size); if (match) FcPatternDestroy (match); } FcPatternDestroy (pat); } #endif if (file.empty ()) ::warning ("unable to find default font files"); else { std::string ascii_file = sys::get_ASCII_filename (file); if (FT_New_Face (m_library, ascii_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. if (FT_Reference_Face (retval) == 0) m_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); m_cache.erase (*pkey); delete pkey; face->generic.data = nullptr; FT_Done_Face (face); } } //-------- static ft_manager *m_instance; // 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 m_cache; FT_Library m_library; bool m_freetype_initialized; bool m_fontconfig_initialized; }; ft_manager *ft_manager::m_instance = nullptr; 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 }; public: ft_text_renderer (void) : base_text_renderer (), m_font (), m_bbox (1, 4, 0.0), m_halign (0), m_xoffset (0), m_line_yoffset (0), m_yoffset (0), m_mode (MODE_BBOX), m_color (dim_vector (1, 3), 0), m_do_strlist (false), m_strlist (), m_line_xoffset (0), m_ymin (0), m_ymax (0), m_deltax (0), m_max_fontsize (0), m_antialias (true) { } // No copying! ft_text_renderer (const ft_text_renderer&) = delete; ft_text_renderer& operator = (const ft_text_renderer&) = delete; ~ft_text_renderer (void) = default; 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 m_pixels; } Matrix get_boundingbox (void) const { return m_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_anti_aliasing (bool val) { m_antialias = val; }; void set_font (const std::string& name, const std::string& weight, const std::string& angle, double size); octave_map get_system_fonts (void); 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: // 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 (), m_face (nullptr) { } ft_font (const std::string& nm, const std::string& wt, const std::string& ang, double sz, FT_Face f = nullptr) : text_renderer::font (nm, wt, ang, sz), m_face (f) { } ft_font (const ft_font& ft); ~ft_font (void) { if (m_face) FT_Done_Face (m_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 m_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, std::string& sub_font); 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 m_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 m_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 m_pixels; // Used to store the bounding box of each line. This is used to layout // multiline text properly. std::list<Matrix> m_line_bbox; // The current horizontal alignment. This is used to align multi-line text. int m_halign; // The X offset for the next glyph. int m_xoffset; // The Y offset of the baseline for the current line. int m_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 m_yoffset; // The current mode of the rendering process (box computing or rendering). int m_mode; // The base color of the rendered text. uint8NDArray m_color; // A list of parsed strings to be used for printing. bool m_do_strlist; std::list<text_renderer::string> m_strlist; // The X offset of the baseline for the current line. int m_line_xoffset; // Min and max y coordinates of all glyphs in a line. FT_Pos m_ymin; FT_Pos m_ymax; // Difference between the advance and the actual extent of the latest glyph FT_Pos m_deltax; // Used for computing the distance between lines. double m_max_fontsize; // Anti-aliasing. bool m_antialias; }; 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 m_font = ft_font (name, weight, angle, size, nullptr); } octave_map ft_text_renderer::get_system_fonts (void) { return ft_manager::get_system_fonts (); } void ft_text_renderer::push_new_line (void) { switch (m_mode) { case MODE_BBOX: { // Create a new bbox entry based on the current font. FT_Face face = m_font.get_face (); if (face) { Matrix bb (1, 5, 0.0); m_line_bbox.push_back (bb); m_xoffset = m_yoffset = 0; m_ymin = m_ymax = m_deltax = 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 = m_line_bbox.front (); m_line_bbox.pop_front (); Matrix new_bbox = m_line_bbox.front (); m_xoffset = m_line_xoffset = compute_line_xoffset (new_bbox); m_line_yoffset -= (-old_bbox(1) + math::round (0.4 * m_max_fontsize) + (new_bbox(3) + new_bbox(1))); m_yoffset = 0; m_ymin = m_ymax = m_deltax = 0; } break; } } int ft_text_renderer::compute_line_xoffset (const Matrix& lb) const { if (! m_bbox.isempty ()) { switch (m_halign) { case 0: return 0; case 1: return (m_bbox(2) - lb(2)) / 2; case 2: return (m_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. m_bbox = Matrix (); switch (m_line_bbox.size ()) { case 0: break; case 1: m_bbox = m_line_bbox.front ().extract (0, 0, 0, 3); break; default: for (const auto& lbox : m_line_bbox) { if (m_bbox.isempty ()) m_bbox = lbox.extract (0, 0, 0, 3); else { double delta = math::round (0.4 * m_max_fontsize) + lbox(3); m_bbox(1) -= delta; m_bbox(3) += delta; m_bbox(2) = math::max (m_bbox(2), lbox(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 (m_mode == MODE_BBOX) { Matrix& bb = m_line_bbox.back (); bb(1) = m_ymin; // Add one pixel to the bbox height to avoid occasional text clipping. // See bug #55328. bb(3) = (m_ymax + 1) - m_ymin; if (m_deltax > 0) bb(2) += m_deltax; } } void ft_text_renderer::set_mode (int m) { m_mode = m; switch (m_mode) { case MODE_BBOX: m_xoffset = m_line_yoffset = m_yoffset = 0; m_max_fontsize = 0.0; m_bbox = Matrix (1, 4, 0.0); m_line_bbox.clear (); push_new_line (); break; case MODE_RENDER: if (m_bbox.numel () != 4) { ::error ("ft_text_renderer: invalid bounding box, cannot render"); m_xoffset = m_line_yoffset = m_yoffset = 0; m_pixels = uint8NDArray (); } else { dim_vector d (4, octave_idx_type (m_bbox(2)), octave_idx_type (m_bbox(3))); m_pixels = uint8NDArray (d, static_cast<uint8_t> (0)); m_xoffset = compute_line_xoffset (m_line_bbox.front ()); m_line_yoffset = -m_bbox(1); m_yoffset = 0; } break; default: error ("ft_text_renderer: invalid mode '%d'", m_mode); break; } } bool is_opaque (const FT_GlyphSlot& glyph, const int x, const int y) { // Borrowed from https://stackoverflow.com/questions/14800827/ // indexing-pixels-in-a-monochrome-freetype-glyph-buffer int pitch = std::abs (glyph->bitmap.pitch); unsigned char *row = &glyph->bitmap.buffer[pitch * y]; char cvalue = row[x >> 3]; return ((cvalue & (128 >> (x & 7))) != 0); } FT_UInt ft_text_renderer::process_character (FT_ULong code, FT_UInt previous, std::string& sub_name) { FT_Face face = m_font.get_face (); sub_name = face->family_name; FT_UInt glyph_index = 0; if (face) { glyph_index = FT_Get_Char_Index (face, code); if (code != '\n' && code != '\t' && (! glyph_index || FT_Load_Glyph (face, glyph_index, FT_LOAD_DEFAULT))) { #if defined (HAVE_FONTCONFIG) // Try to substitue font FT_Face sub_face = ft_manager::get_font (m_font.get_name (), m_font.get_weight (), m_font.get_angle (), m_font.get_size (), code); if (sub_face) { FT_Set_Char_Size (sub_face, 0, m_font.get_size ()*64, 0, 0); glyph_index = FT_Get_Char_Index (sub_face, code); if (glyph_index && (FT_Load_Glyph (sub_face, glyph_index, FT_LOAD_DEFAULT) == 0)) { static std::string prev_sub_name; if (prev_sub_name.empty () || prev_sub_name != std::string (sub_face->family_name)) { prev_sub_name = sub_face->family_name; warning_with_id ("Octave:substituted-glyph", "text_renderer: substituting font to '%s' for some characters", sub_face->family_name); } ft_font saved_font = m_font; m_font = ft_font (m_font.get_name (), m_font.get_weight (), m_font.get_angle (), m_font.get_size (), sub_face); process_character (code, previous, sub_name); m_font = saved_font; } else { glyph_index = 0; warn_missing_glyph (code); } } else { glyph_index = 0; warn_missing_glyph (code); } #else glyph_index = 0; warn_missing_glyph (code); #endif } else if ((code == '\n') || (code == '\t')) { 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 if (code == '\n') push_new_line (); else { // Advance to next multiple of 4 times the width of the "space" // character. int x_tab = 4 * (face->glyph->advance.x >> 6); m_xoffset = (1 + std::floor (1. * m_xoffset / x_tab)) * x_tab; } } else { switch (m_mode) { case MODE_RENDER: if (FT_Render_Glyph (face->glyph, (m_antialias ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO))) { 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); m_xoffset += (delta.x >> 6); } x0 = m_xoffset + face->glyph->bitmap_left; y0 = m_line_yoffset + m_yoffset + (face->glyph->bitmap_top - 1); // 'w' seems to have a negative -1 // face->glyph->bitmap_left, this is so we don't index out // of bound, and assumes we've 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 = (m_antialias ? bitmap.buffer[r*bitmap.width+c] : (is_opaque (face->glyph, c, r) ? 255 : 0)); if (x0+c < 0 || x0+c >= m_pixels.dim2 () || y0-r < 0 || y0-r >= m_pixels.dim3 ()) { // ::warning ("ft_text_renderer: x %d, y %d", // x0+c, y0-r); } else if (m_pixels(3, x0+c, y0-r).value () == 0) { m_pixels(0, x0+c, y0-r) = m_color(0); m_pixels(1, x0+c, y0-r) = m_color(1); m_pixels(2, x0+c, y0-r) = m_color(2); m_pixels(3, x0+c, y0-r) = pix; } } m_xoffset += (face->glyph->advance.x >> 6); } break; case MODE_BBOX: Matrix& bb = m_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); m_xoffset += (delta.x >> 6); } // Extend current X offset box by the width of the current // glyph. Then extend the line bounding box if necessary. m_xoffset += (face->glyph->advance.x >> 6); bb(2) = math::max (bb(2), m_xoffset); // Store the actual bbox vertical coordinates of this character FT_Glyph glyph; if (FT_Get_Glyph (face->glyph, &glyph)) warn_glyph_render (code); else { FT_BBox glyph_bbox; FT_Glyph_Get_CBox (glyph, FT_GLYPH_BBOX_UNSCALED, &glyph_bbox); m_deltax = (glyph_bbox.xMax - face->glyph->advance.x) >> 6; m_ymin = math::min ((glyph_bbox.yMin >> 6) + m_yoffset, m_ymin); m_ymax = math::max ((glyph_bbox.yMax >> 6) + m_yoffset, m_ymax); FT_Done_Glyph (glyph); update_line_bbox (); } 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 m_strlist = std::list<text_renderer::string> (); unwind_protect_var<bool> restore_var1 (m_do_strlist); unwind_protect_var<std::list<text_renderer::string>> restore_var2 (m_strlist); m_do_strlist = true; text_to_pixels (txt, pxls, box, ha, va, rot, interp, false); lst = m_strlist; } void ft_text_renderer::visit (text_element_string& e) { if (m_font.is_valid ()) { m_max_fontsize = std::max (m_max_fontsize, m_font.get_size ()); FT_UInt glyph_index, previous = 0; std::string str = e.string_value (); const uint8_t *c = reinterpret_cast<const uint8_t *> (str.c_str ()); uint32_t u32_c; std::size_t n = str.size (); std::size_t icurr = 0; std::size_t ibegin = 0; // Initialize a new string text_renderer::string fs (str, m_font, m_xoffset, m_yoffset); std::string fname = m_font.get_face ()->family_name; if (fname.find (" ") != std::string::npos) fname = "'" + fname + "'"; fs.set_family (fname); std::vector<double> xdata; std::string sub_name; while (n > 0) { // Retrieve the length and the u32 representation of the current // character int mblen = octave_u8_strmbtouc_wrapper (&u32_c, c + icurr); if (mblen < 1) { // This is not an UTF-8 character, use a replacement character mblen = 1; u32_c = 0xFFFD; } n -= mblen; if (m_do_strlist && m_mode == MODE_RENDER) { if (u32_c == 10) { // Finish previous string in m_strlist before processing // the newline character fs.set_y (m_line_yoffset + m_yoffset); fs.set_color (m_color); std::string s = str.substr (ibegin, icurr - ibegin); if (! s.empty ()) { fs.set_string (s); fs.set_y (m_line_yoffset + m_yoffset); fs.set_xdata (xdata); fs.set_family (fname); m_strlist.push_back (fs); } } else xdata.push_back (m_xoffset); } glyph_index = process_character (u32_c, previous, sub_name); if (m_do_strlist && m_mode == MODE_RENDER && ! sub_name.empty ()) { // Add substitution font to the family name stack std::string tmp_family = fs.get_family (); if (tmp_family.find (sub_name) == std::string::npos) { if (sub_name.find (" ") != std::string::npos) sub_name = "'" + sub_name + "'"; fs.set_family (tmp_family + ", " + sub_name); } } if (u32_c == 10) { previous = 0; if (m_do_strlist && m_mode == MODE_RENDER) { // Start a new string in m_strlist ibegin = icurr+1; xdata.clear (); fs = text_renderer::string (str.substr (ibegin), m_font, m_line_xoffset, m_yoffset); } } else previous = glyph_index; icurr += mblen; } if (m_do_strlist && m_mode == MODE_RENDER && ! fs.get_string ().empty ()) { fs.set_y (m_line_yoffset + m_yoffset); fs.set_color (m_color); fs.set_xdata (xdata); m_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 (m_font); uint8NDArray saved_color (m_color); text_processor::visit (e); m_font = saved_font; m_color = saved_color; } void ft_text_renderer::visit (text_element_subscript& e) { ft_font saved_font (m_font); int saved_line_yoffset = m_line_yoffset; int saved_yoffset = m_yoffset; double sz = m_font.get_size (); // Reducing font size by 70% produces decent results. set_font (m_font.get_name (), m_font.get_weight (), m_font.get_angle (), std::max (5.0, sz * 0.7)); if (m_font.is_valid ()) { // Shifting the baseline by 15% of the font size gives decent results. m_yoffset -= std::ceil (sz * 0.15); if (m_mode == MODE_BBOX) update_line_bbox (); } text_processor::visit (e); m_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 (m_line_yoffset == saved_line_yoffset) m_yoffset = saved_yoffset; } void ft_text_renderer::visit (text_element_superscript& e) { ft_font saved_font (m_font); int saved_line_yoffset = m_line_yoffset; int saved_yoffset = m_yoffset; double sz = m_font.get_size (); // Reducing font size by 70% produces decent results. set_font (m_font.get_name (), m_font.get_weight (), m_font.get_angle (), std::max (5.0, sz * 0.7)); if (saved_font.is_valid ()) { // Shifting the baseline by 40% of the font size gives decent results. m_yoffset += std::ceil (sz * 0.4); if (m_mode == MODE_BBOX) update_line_bbox (); } text_processor::visit (e); m_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 (m_line_yoffset == saved_line_yoffset) m_yoffset = saved_yoffset; } void ft_text_renderer::visit (text_element_color& e) { if (m_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 (m_font.get_name (), m_font.get_weight (), m_font.get_angle (), sz); if (m_mode == MODE_BBOX) update_line_bbox (); } void ft_text_renderer::visit (text_element_fontname& e) { set_font (e.get_fontname (), m_font.get_weight (), m_font.get_angle (), m_font.get_size ()); if (m_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 (m_font.get_name (), "normal", "normal", m_font.get_size ()); break; case text_element_fontstyle::bold: set_font (m_font.get_name (), "bold", "normal", m_font.get_size ()); break; case text_element_fontstyle::italic: set_font (m_font.get_name (), "normal", "italic", m_font.get_size ()); break; case text_element_fontstyle::oblique: set_font (m_font.get_name (), "normal", "oblique", m_font.get_size ()); break; } if (m_mode == MODE_BBOX) update_line_bbox (); } void ft_text_renderer::visit (text_element_symbol& e) { uint32_t code = e.get_symbol_code (); std::vector<double> xdata (1, m_xoffset); text_renderer::string fs ("-", m_font, m_xoffset, m_yoffset); if (code != text_element_symbol::invalid_code && m_font.is_valid ()) { std::string sub_name; process_character (code, 0, sub_name); if (m_do_strlist && m_mode == MODE_RENDER) { if (! sub_name.empty ()) { // Add substitution font to the family name std::string tmp_family = fs.get_family (); if (tmp_family.find (sub_name) == std::string::npos) { if (sub_name.find (" ") != std::string::npos) sub_name = "'" + sub_name + "'"; fs.set_family (tmp_family + ", " + sub_name); } } fs.set_code (code); fs.set_xdata (xdata); } } else if (m_font.is_valid ()) ::warning ("ignoring unknown symbol: %d", e.get_symbol ()); if (m_do_strlist && m_mode == MODE_RENDER && fs.get_code ()) { fs.set_y (m_line_yoffset + m_yoffset); fs.set_color (m_color); fs.set_family (m_font.get_face ()->family_name); m_strlist.push_back (fs); } } void ft_text_renderer::visit (text_element_combined& e) { int saved_xoffset = m_xoffset; int max_xoffset = m_xoffset; for (auto *txt_elt : e) { m_xoffset = saved_xoffset; txt_elt->accept (*this); max_xoffset = math::max (m_xoffset, max_xoffset); } m_xoffset = max_xoffset; } void ft_text_renderer::reset (void) { set_mode (MODE_BBOX); set_color (Matrix (1, 3, 0.0)); m_strlist = std::list<text_renderer::string> (); } void ft_text_renderer::set_color (const Matrix& c) { if (c.numel () == 3) { m_color(0) = static_cast<uint8_t> (c(0)*255); m_color(1) = static_cast<uint8_t> (c(1)*255); m_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 = m_bbox; set_mode (MODE_RENDER); if (m_pixels.numel () > 0) { elt->accept (*this); rotate_pixels (m_pixels, rotation); } return m_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) = m_bbox(2); extent(1) = m_bbox(3); break; case ROTATION_90: case ROTATION_270: extent(0) = m_bbox(3); extent(1) = m_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; } 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); m_halign = _halign; text_element *elt = text_parser::parse (txt, interpreter); pxls = render (elt, box, rot_mode); delete elt; if (pxls.isempty ()) return; // nothing to render // Move X0 and Y0 depending on alignments and eventually swap all values // for text rotated 90° 180° or 270° fix_bbox_anchor (box, m_halign, valign, rot_mode, handle_rotation); } ft_text_renderer::ft_font::ft_font (const ft_font& ft) : text_renderer::font (ft), m_face (nullptr) { #if defined (HAVE_FT_REFERENCE_FACE) FT_Face ft_face = ft.get_face (); if (ft_face && FT_Reference_Face (ft_face) == 0) m_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 (m_face) { FT_Done_Face (m_face); m_face = nullptr; } #if defined (HAVE_FT_REFERENCE_FACE) FT_Face ft_face = ft.get_face (); if (ft_face && FT_Reference_Face (ft_face) == 0) m_face = ft_face; #endif } return *this; } FT_Face ft_text_renderer::ft_font::get_face (void) const { if (! m_face && ! m_name.empty ()) { m_face = ft_manager::get_font (m_name, m_weight, m_angle, m_size); if (m_face) { if (FT_Set_Char_Size (m_face, 0, m_size*64, 0, 0)) ::warning ("ft_text_renderer: unable to set font size to %g", m_size); } else ::warning ("ft_text_renderer: unable to load appropriate font"); } return m_face; } } #endif namespace octave { base_text_renderer * make_ft_text_renderer (void) { #if defined (HAVE_FREETYPE) return new ft_text_renderer (); #else return 0; #endif } }