Mercurial > octave
diff libinterp/corefcn/ft-text-renderer.cc @ 22331:b81b08cc4c83
maint: Indent namespaces in more files.
author | John W. Eaton <jwe@octave.org> |
---|---|
date | Wed, 17 Aug 2016 11:43:27 -0400 |
parents | 71dd9d5a5ecd |
children | 34ce5be04942 |
line wrap: on
line diff
--- a/libinterp/corefcn/ft-text-renderer.cc Wed Aug 17 08:20:26 2016 -0700 +++ b/libinterp/corefcn/ft-text-renderer.cc Wed Aug 17 11:43:27 2016 -0400 @@ -89,7 +89,6 @@ namespace octave { - class ft_manager { @@ -324,1053 +323,1051 @@ namespace octave { - -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 + class + OCTINTERP_API + ft_text_renderer : public base_text_renderer { public: - ft_font (void) - : text_renderer::font (), face (0) { } + 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); - 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) - { } + 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; } - ft_font (const ft_font& ft); + 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); - ~ft_font (void) + 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 { - if (face) - FT_Done_Face (face); - } + 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& operator = (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; + }; - bool is_valid (void) const { return get_face (); } + void push_new_line (void); + + void update_line_bbox (void); + + void compute_bbox (void); + + int compute_line_xoffset (const Matrix& lb) const; - FT_Face get_face (void) 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: - mutable FT_Face face; + // 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 push_new_line (void); + 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 (); - void update_line_bbox (void); + 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. - void compute_bbox (void); + 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 compute_line_xoffset (const Matrix& lb) const; + 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; - FT_UInt process_character (FT_ULong code, FT_UInt previous = 0); + 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) = octave::math::max (bbox(2), (*it)(2)); + } + } + break; + } + } -public: + 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)); - 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); + 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"); -private: + xoffset = line_yoffset = yoffset = 0; + pixels = uint8NDArray (); + } + else + { + dim_vector d (4, octave_idx_type (bbox(2)), + octave_idx_type (bbox(3))); + pixels = uint8NDArray (d, 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); - // The current font used by the renderer. - ft_font font; + 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've allocated + // the right amount of horizontal space in the bbox. + if (x0 < 0) + x0 = 0; - // 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; + 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); + } - // 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; + // 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) = octave::math::max (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 = std::mbrtowc (&wc, str.data () + curr, n, &ps); - // Used to store the bounding box of each line. This is used to layout - // multiline text properly. - std::list<Matrix> line_bbox; + 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); - // The current horizontal alignment. This is used to align multi-line text. - int halign; + 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. - // The X offset for the next glyph. - int xoffset; + 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; - // The Y offset of the baseline for the current line. - int line_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 ()); - // 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; + 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 ()); - // The current mode of the rendering process (box computing or rendering). - int mode; + 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 = octave::math::max (xoffset, max_xoffset); + } + + xoffset = max_xoffset; + } - // The base color of the rendered text. - uint8NDArray color; + void + ft_text_renderer::reset (void) + { + set_mode (MODE_BBOX); + set_color (Matrix (1, 3, 0.0)); + } - // A list of parsed strings to be used for printing. - std::list<text_renderer::string> strlist; + 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); - // The X offset of the baseline for the current line. - int line_xoffset; + 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; + } + } -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 + 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; - font = ft_font (name, weight, angle, size, 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; -void -ft_text_renderer::push_new_line (void) -{ - switch (mode) - { - case MODE_BBOX: + 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) { - // Create a new bbox entry based on the current font. + 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; - FT_Face face = font.get_face (); + 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) { - 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; + FT_Done_Face (face); + face = 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) = octave::math::max (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 - { - dim_vector d (4, octave_idx_type (bbox(2)), - octave_idx_type (bbox(3))); - pixels = uint8NDArray (d, 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'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 = 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) = octave::math::max (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 = std::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 = octave::math::max (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 (); + FT_Face ft_face = ft.get_face (); - if (ft_face && FT_Reference_Face (ft_face) == 0) - face = ft_face; + if (ft_face && FT_Reference_Face (ft_face) == 0) + face = ft_face; #endif - } + } - return *this; -} + 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); + 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"); - } + 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; -} - + return face; + } } #endif