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