changeset 12965:22bc9ec80c2c

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.
author Ben Abbott <bpabbott@mac.com>
date Mon, 15 Aug 2011 10:24:09 -0400
parents 8ec12d686796
children a4eb4d6bbd61
files scripts/plot/private/__axis_label__.m scripts/plot/private/__go_draw_axes__.m scripts/plot/text.m src/gl-render.cc src/gl2ps-renderer.cc src/graphics.cc src/graphics.h.in src/txt-eng-ft.cc
diffstat 8 files changed, 246 insertions(+), 87 deletions(-) [+]
line wrap: on
line diff
--- a/scripts/plot/private/__axis_label__.m
+++ b/scripts/plot/private/__axis_label__.m
@@ -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
--- a/scripts/plot/private/__go_draw_axes__.m
+++ b/scripts/plot/private/__go_draw_axes__.m
@@ -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");
--- a/scripts/plot/text.m
+++ b/scripts/plot/text.m
@@ -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)
+
--- a/src/gl-render.cc
+++ b/src/gl-render.cc
@@ -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 ());
--- a/src/gl2ps-renderer.cc
+++ b/src/gl2ps-renderer.cc
@@ -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
--- a/src/graphics.cc
+++ b/src/graphics.cc
@@ -4341,7 +4341,7 @@
   text::properties& xlabel_props = reinterpret_cast<text::properties&>
     (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<text::properties&>
     (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") ||
--- a/src/graphics.h.in
+++ b/src/graphics.h.in
@@ -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; }
--- a/src/txt-eng-ft.cc
+++ b/src/txt-eng-ft.cc
@@ -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;
             }
         }
     }