# HG changeset patch # User Ben Abbott # Date 1313418249 14400 # Node ID 22bc9ec80c2c0acf2003461e2bbf27ff6c0b39d0 # Parent 8ec12d6867961d0b70f5908127e47daf533c22a1 allow multi-line string property for text objects using cell arrays or char matrices * __axis_label__.m: Don't check type of txt argument. * __go_draw_axes__.m: Handle multi-line string property for text objects. * text.m: Likewise. * gl2ps-renderer.cc (glps_renderer::draw_text): Handle text::properties string property as octave_value object that can contain either a char array or cellstr object. * graphics.cc (axes::properties::update_xlabel_position, axes::properties::update_ylabel_position, axes::properties::update_zlabel_position, axes::properties::get_extent, text::properties::update_text_extent): Likewise. * graphics.h.in (text_label_property::do_set): Don't forget to set stored_type when value is a cell. (text::properties::get_string): Delete custom getter. diff -r 8ec12d686796 -r 22bc9ec80c2c scripts/plot/private/__axis_label__.m --- a/scripts/plot/private/__axis_label__.m Mon Aug 15 10:05:28 2011 -0400 +++ b/scripts/plot/private/__axis_label__.m Mon Aug 15 10:24:09 2011 -0400 @@ -25,24 +25,20 @@ function retval = __axis_label__ (caller, txt, varargin) - if (ischar (txt)) - ca = gca (); + ca = gca (); - h = get (gca (), caller); + h = get (gca (), caller); - set (h, "fontangle", get (ca, "fontangle"), - "fontname", get (ca, "fontname"), - "fontsize", get (ca, "fontsize"), - "fontunits", get (ca, "fontunits"), - "fontweight", get (ca, "fontweight"), - "string", txt, - varargin{:}); + set (h, "fontangle", get (ca, "fontangle"), + "fontname", get (ca, "fontname"), + "fontsize", get (ca, "fontsize"), + "fontunits", get (ca, "fontunits"), + "fontweight", get (ca, "fontweight"), + "string", txt, + varargin{:}); - if (nargout > 0) - retval = h; - endif - else - error ("%s: expecting first argument to be character string", caller); + if (nargout > 0) + retval = h; endif endfunction diff -r 8ec12d686796 -r 22bc9ec80c2c scripts/plot/private/__go_draw_axes__.m --- a/scripts/plot/private/__go_draw_axes__.m Mon Aug 15 10:05:28 2011 -0400 +++ b/scripts/plot/private/__go_draw_axes__.m Mon Aug 15 10:24:09 2011 -0400 @@ -1250,6 +1250,11 @@ colorspec = get_text_colorspec (color, mono); endif + if (ischar (obj.string)) + num_lines = size (obj.string, 1); + else + num_lines = numel (obj.string); + endif switch valign ## Text offset in characters. This relies on gnuplot for font metrics. case "top" @@ -1257,17 +1262,18 @@ case "cap" dy = -0.5; case "middle" - dy = 0; + dy = 0.5 * (num_lines - 1); case "baseline" - dy = 0.5; + dy = 0.5 + (num_lines - 1); case "bottom" - dy = 0.5; + dy = 0.5 + (num_lines - 1); endswitch ## Gnuplot's Character units are different for x/y and vary with fontsize. The aspect ratio ## of 1:1.7 was determined by experiment to work for eps/ps/etc. For the MacOS aqua terminal ## a value of 2.5 is needed. However, the difference is barely noticable. dx_and_dy = [(-dy * sind (angle)), (dy * cosd(angle))] .* [1.7 1]; + ## FIXME - Multiline text produced the gnuplot "warning: ft_render: skipping glyph" if (nd == 3) ## This produces the desired vertical alignment in 3D. fprintf (plot_stream, @@ -2129,10 +2135,31 @@ bld = false; endif + ## The text object maybe multiline, and may be of any class str = getfield (obj, fld); + if (ischar (str) && size (str, 1) > 1) + str = cellstr (str); + elseif (isnumeric (str)) + str = cellstr (num2str (str(:))); + endif + if (iscellstr (str)) + for n = 1:numel(str) + if (isnumeric (str{n})) + str{n} = num2str (str{n}); + endif + endfor + str = sprintf ("%s\n", str{:})(1:end-1); + endif + if (enhanced) if (strcmpi (obj.interpreter, "tex")) - str = __tex2enhanced__ (str, fnt, it, bld); + if (iscellstr (str)) + for n = 1:numel(str) + str{n} = __tex2enhanced__ (str{n}, fnt, it, bld); + endfor + else + str = __tex2enhanced__ (str, fnt, it, bld); + endif elseif (strcmpi (obj.interpreter, "latex")) if (! warned_latex) warning ("latex markup not supported for text objects"); diff -r 8ec12d686796 -r 22bc9ec80c2c scripts/plot/text.m --- a/scripts/plot/text.m Mon Aug 15 10:05:28 2011 -0400 +++ b/scripts/plot/text.m Mon Aug 15 10:24:09 2011 -0400 @@ -47,15 +47,33 @@ endif label = varargin{offset}; - if (ischar (label) || iscellstr (label)) - varargin(1:offset) = []; - if (ischar (label)) + varargin(1:offset) = []; + + nx = numel (x); + ny = numel (y); + nz = numel (z); + if (ischar (label) || isnumeric (label)) + nt = size (label, 1); + if (nx > 1 && nt == 1) + ## Mutiple text objects with same string + label = repmat ({label}, [nx, 1]); + nt = nx; + elseif (nx > 1 && nt == nx) + ## Mutiple text objects with different strings label = cellstr (label); + elseif (ischar (label)) + ## Single text object with one or more lines + label = {label}; endif - n = numel (label); - nx = numel (x); - ny = numel (y); - nz = numel (z); + elseif (iscell (label)) + nt = numel (label); + if (nx > 1 && nt == 1) + label = repmat ({label}, [nx, 1]); + nt = nx; + elseif (nx > 1 && nt == nx) + else + label = {label}; + endif else error ("text: expecting LABEL to be a character string or cell array of character strings"); endif @@ -63,7 +81,7 @@ x = y = z = 0; nx = ny = nz = 1; label = {""}; - n = 1; + nt = 1; endif if (rem (numel (varargin), 2) == 0) @@ -71,20 +89,18 @@ if (nx == ny && nx == nz) pos = [x(:), y(:), z(:)]; ca = gca (); - tmp = zeros (n, 1); - if (n == 1) - label = label{1}; - for i = 1:nx - tmp(i) = __go_text__ (ca, "string", label, + tmp = zeros (nt, 1); + if (nx == 1) + ## TODO - Modify __go_text__() to accept cell-strings + tmp = __go_text__ (ca, "string", "foobar", + varargin{:}, + "position", pos); + set (tmp, "string", label{1}); + elseif (nt == nx) + for n = 1:nt + tmp(n) = __go_text__ (ca, "string", label{n}, varargin{:}, - "position", pos(i,:)); - endfor - __request_drawnow__ (); - elseif (n == nx) - for i = 1:nx - tmp(i) = __go_text__ (ca, "string", label{i}, - varargin{:}, - "position", pos(i,:)); + "position", pos(n,:)); endfor __request_drawnow__ (); else @@ -142,3 +158,54 @@ %! endfor %! caxis ([-100 100]) %! title ("Vertically Aligned at Bottom") + +%!demo +%! clf +%! axis ([0 8 0 8]) +%! title (["First title";"Second title"]) +%! xlabel (["First xlabel";"Second xlabel"]) +%! ylabel (["First ylabel";"Second ylabel"]) +%! text (4, 4, {"Hello", "World"}, ... +%! "horizontalalignment", "center", ... +%! "verticalalignment", "middle") +%! grid on + +%!demo +%! clf +%! h = mesh (peaks, "edgecolor", 0.7 * [1 1 1], ... +%! "facecolor", "none", ... +%! "facealpha", 0); +%! title (["First title";"Second title"]) +%! xlabel (["First xlabel";"Second xlabel"]) +%! ylabel (["First ylabel";"Second ylabel"]) +%! zlabel (["First zlabel";"Second zlabel"]) +%! text (0, 0, 5, {"Hello", "World"}, ... +%! "horizontalalignment", "center", ... +%! "verticalalignment", "middle") +%! hold on +%! plot3 (0, 0, 5, "+k") +%! + +%!demo +%! clf +%! h = text (0.6, 0.3, "char"); +%! assert ("char", class (get (h, "string"))) +%! h = text (0.6, 0.4, ["char row 1"; "char row 2"]); +%! assert ("char", class (get (h, "string"))) +%! h = text (0.6, 0.6, {"cell2str (1,1)", "cell2str (1,2)"; "cell2str (2,1)", "cell2str (2,2)"}); +%! assert ("cell", class (get (h, "string"))) +%! h = text (0.6, 0.8, "foobar"); +%! set (h, "string", 1:3) +%! h = text ([0.2, 0.2], [0.3, 0.4], "one string & two objects"); +%! assert ("char", class (get (h(1), "string"))) +%! assert ("char", class (get (h(2), "string"))) +%! h = text ([0.2, 0.2], [0.5, 0.6], {"one cellstr & two objects"}); +%! assert ("cell", class (get (h(1), "string"))) +%! assert ("cell", class (get (h(2), "string"))) +%! h = text ([0.2, 0.2], [0.7, 0.8], {"cellstr 1 object 1", "cellstr 2 object 2"}); +%! assert ("char", class (get (h(1), "string"))) +%! assert ("char", class (get (h(2), "string"))) +%! xlabel (1:2) +%! ylabel (1:2) +%! title (1:2) + diff -r 8ec12d686796 -r 22bc9ec80c2c src/gl-render.cc --- a/src/gl-render.cc Mon Aug 15 10:05:28 2011 -0400 +++ b/src/gl-render.cc Mon Aug 15 10:24:09 2011 -0400 @@ -2422,7 +2422,7 @@ void opengl_renderer::draw_text (const text::properties& props) { - if (props.get_string ().empty ()) + if (props.get_string ().is_empty ()) return; const Matrix pos = xform.scale (props.get_data_position ()); diff -r 8ec12d686796 -r 22bc9ec80c2c src/gl2ps-renderer.cc --- a/src/gl2ps-renderer.cc Mon Aug 15 10:05:28 2011 -0400 +++ b/src/gl2ps-renderer.cc Mon Aug 15 10:24:09 2011 -0400 @@ -199,7 +199,7 @@ void glps_renderer::draw_text (const text::properties& props) { - if (props.get_string ().empty ()) + if (props.get_string ().is_empty ()) return; set_font (props); @@ -223,9 +223,15 @@ // FIXME: handle margin and surrounding box glRasterPos3d (pos(0), pos(1), pos(2)); - gl2psTextOpt (props.get_string ().c_str (), fontname.c_str (), fontsize, + + octave_value string_prop = props.get_string (); + + string_vector sv = string_prop.all_strings (); + + std::string s = sv.join ("\n"); + + gl2psTextOpt (s.c_str (), fontname.c_str (), fontsize, alignment_to_mode (halign, valign), props.get_rotation ()); - } #endif diff -r 8ec12d686796 -r 22bc9ec80c2c src/graphics.cc --- a/src/graphics.cc Mon Aug 15 10:05:28 2011 -0400 +++ b/src/graphics.cc Mon Aug 15 10:24:09 2011 -0400 @@ -4341,7 +4341,7 @@ text::properties& xlabel_props = reinterpret_cast (gh_manager::get_object (get_xlabel ()).get_properties ()); - bool is_empty = xlabel_props.get_string ().empty (); + bool is_empty = xlabel_props.get_string ().is_empty (); unwind_protect frame; frame.protect_var (updating_xlabel_position); @@ -4432,7 +4432,7 @@ text::properties& ylabel_props = reinterpret_cast (gh_manager::get_object (get_ylabel ()).get_properties ()); - bool is_empty = ylabel_props.get_string ().empty (); + bool is_empty = ylabel_props.get_string ().is_empty (); unwind_protect frame; frame.protect_var (updating_ylabel_position); @@ -4524,7 +4524,7 @@ (gh_manager::get_object (get_zlabel ()).get_properties ()); bool camAuto = cameraupvectormode_is ("auto"); - bool is_empty = zlabel_props.get_string ().empty (); + bool is_empty = zlabel_props.get_string ().is_empty (); unwind_protect frame; frame.protect_var (updating_zlabel_position); @@ -4896,7 +4896,7 @@ Matrix text_pos = text_props.get_position ().matrix_value (); text_pos = xform.transform (text_pos(0), text_pos(1), text_pos(2)); - if (text_props.get_string ().empty ()) + if (text_props.get_string ().is_empty ()) { ext(0) = std::min (ext(0), text_pos(0)); ext(1) = std::min (ext(1), text_pos(1)); @@ -6006,6 +6006,7 @@ text::properties::update_text_extent (void) { #ifdef HAVE_FREETYPE + int halign = 0, valign = 0; if (horizontalalignment_is ("center")) @@ -6021,11 +6022,17 @@ valign = 1; Matrix bbox; + // FIXME: string should be parsed only when modified, for efficiency - renderer.text_to_pixels (get_string (), pixels, bbox, + + octave_value string_prop = get_string (); + + string_vector sv = string_prop.all_strings (); + + renderer.text_to_pixels (sv.join ("\n"), pixels, bbox, halign, valign, get_rotation ()); - set_extent (bbox); + #endif if (autopos_tag_is ("xlabel") || autopos_tag_is ("ylabel") || diff -r 8ec12d686796 -r 22bc9ec80c2c src/graphics.h.in --- a/src/graphics.h.in Mon Aug 15 10:05:28 2011 -0400 +++ b/src/graphics.h.in Mon Aug 15 10:24:09 2011 -0400 @@ -753,6 +753,12 @@ : base_property (p), value (p.value), stored_type (p.stored_type) { } + bool empty (void) const + { + octave_value tmp = get (); + return tmp.is_empty (); + } + octave_value get (void) const { if (stored_type == char_t) @@ -818,6 +824,8 @@ return false; } } + + stored_type = cellstr_t; } else { @@ -3851,7 +3859,7 @@ // properties declarations. BEGIN_PROPERTIES (text) - text_label_property string gu , "" + text_label_property string u , "" radio_property units u , "{data}|pixels|normalized|inches|centimeters|points" array_property position mu , Matrix (1, 3, 0.0) double_property rotation mu , 0 @@ -3888,8 +3896,6 @@ radio_property autopos_tag h , "{none}|xlabel|ylabel|zlabel|title" END_PROPERTIES - std::string get_string (void) const { return string.string_value (); } - Matrix get_data_position (void) const; Matrix get_extent_matrix (void) const; const uint8NDArray& get_pixels (void) const { return pixels; } diff -r 8ec12d686796 -r 22bc9ec80c2c src/txt-eng-ft.cc --- a/src/txt-eng-ft.cc Mon Aug 15 10:05:28 2011 -0400 +++ b/src/txt-eng-ft.cc Mon Aug 15 10:24:09 2011 -0400 @@ -270,6 +270,7 @@ { if (face) { + FT_UInt box_line_width = 0; std::string str = e.string_value (); FT_UInt glyph_index, previous = 0; @@ -277,8 +278,9 @@ { glyph_index = FT_Get_Char_Index (face, str[i]); - if (! glyph_index - || FT_Load_Glyph (face, glyph_index, FT_LOAD_DEFAULT)) + if (str[i] != '\n' + && (! glyph_index + || FT_Load_Glyph (face, glyph_index, FT_LOAD_DEFAULT))) ::warning ("ft_render: skipping missing glyph for character `%c'", str[i]); else @@ -286,7 +288,20 @@ switch (mode) { case MODE_RENDER: - if (FT_Render_Glyph (face->glyph, FT_RENDER_MODE_NORMAL)) + if (str[i] == '\n') + { + glyph_index = FT_Get_Char_Index(face, ' '); + if (!glyph_index || FT_Load_Glyph (face, glyph_index, FT_LOAD_DEFAULT)) + { + ::warning ("ft_render: skipping missing glyph for character ` '"); + } + else + { + xoffset = 0; + yoffset -= (face->size->metrics.height >> 6); + } + } + else if (FT_Render_Glyph (face->glyph, FT_RENDER_MODE_NORMAL)) ::warning ("ft_render: unable to render glyph for character `%c'", str[i]); else @@ -304,6 +319,14 @@ x0 = xoffset+face->glyph->bitmap_left; y0 = 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; r < bitmap.rows; r++) for (int c = 0; c < bitmap.width; c++) { @@ -327,43 +350,70 @@ break; case MODE_BBOX: - // width - if (previous) + if (str[i] == '\n') { - FT_Vector delta; - - FT_Get_Kerning (face, previous, glyph_index, FT_KERNING_DEFAULT, &delta); - bbox(2) += (delta.x >> 6); - } - bbox(2) += (face->glyph->advance.x >> 6); - - int asc, desc; - - if (false /*tight*/) + glyph_index = FT_Get_Char_Index(face, ' '); + if (! glyph_index + || FT_Load_Glyph (face, glyph_index, FT_LOAD_DEFAULT)) + { + ::warning ("ft_render: skipping missing glyph for character ` '"); + } + else + { + // Reset the pixel width for this newline, so we don't + // allocate a bounding box larger than the horizontal + // width of the multi-line + box_line_width = 0; + bbox(1) -= (face->size->metrics.height >> 6); + } + } + else { - desc = face->glyph->metrics.horiBearingY - face->glyph->metrics.height; - asc = face->glyph->metrics.horiBearingY; - } - else - { - asc = face->size->metrics.ascender; - desc = face->size->metrics.descender; - } + // width + if (previous) + { + FT_Vector delta; + + FT_Get_Kerning (face, previous, glyph_index, + FT_KERNING_DEFAULT, &delta); + + box_line_width += (delta.x >> 6); + } + + box_line_width += (face->glyph->advance.x >> 6); + + int asc, desc; - asc = yoffset + (asc >> 6); - desc = yoffset + (desc >> 6); + if (false /*tight*/) + { + desc = face->glyph->metrics.horiBearingY - face->glyph->metrics.height; + asc = face->glyph->metrics.horiBearingY; + } + else + { + asc = face->size->metrics.ascender; + desc = face->size->metrics.descender; + } - if (desc < bbox(1)) - { - bbox(3) += (bbox(1) - desc); - bbox(1) = desc; - } - if (asc > (bbox(3)+bbox(1))) - bbox(3) = asc-bbox(1); + asc = yoffset + (asc >> 6); + desc = yoffset + (desc >> 6); + + if (desc < bbox(1)) + { + bbox(3) += (bbox(1) - desc); + bbox(1) = desc; + } + if (asc > (bbox(3)+bbox(1))) + bbox(3) = asc-bbox(1); + if (bbox(2) < box_line_width) + bbox(2) = box_line_width; + } break; } - - previous = glyph_index; + if (str[i] == '\n') + previous = 0; + else + previous = glyph_index; } } }