changeset 17271:cb7233cfbf43

Prepare base text rendering code for full TeX support. * libinterp/corefcm/oct-tex-lexer.ll: Include newlines in string characters. * libinterp/corefcn/txt-eng-ft.h (list): New include. (class ft_render::ft_font): New class. (ft_render::current_face, ft_render::push_new_line, ft_render::compute_bbox, ft_render::compute_line_xoffset): New methods. (ft_render::fonts): New member, replaces obsolete face. (ft_render::line_bbox): New member. (ft_render::halign): New member, replaces obsolete multiline_halign. (ft_render::multiline_align_xoffsets): Remove member. (class ft_render): Add comments for each class member. * libinterp/corefcn/txt-eng-ft.cc (ft_render::text_to_pixels): Use halig instead of multiline_halign. (ft_render::ft_render): Adapt to added/removed members. (ft_render::~ft_render): Likewise. (ft_render::set_font): Use fonts member instead of face. (ft_render::push_new_line, ft_render::compute_bbox, ft_render::compute_line_xoffset): New methods. (ft_render::render, ft_render::get_extent): Call compute_bbox. (ft_render::visit(text_element_string)): Remove code for handling newlines, call push_new_line instead. Remove code related to multiline_align_xoffsets.
author Michael Goffioul <michael.goffioul@gmail.com>
date Sun, 18 Aug 2013 16:36:44 -0400
parents ba865ea9c7e9
children 8ce6cdd272eb
files libinterp/corefcn/oct-tex-lexer.ll libinterp/corefcn/txt-eng-ft.cc libinterp/corefcn/txt-eng-ft.h
diffstat 3 files changed, 237 insertions(+), 99 deletions(-) [+]
line wrap: on
line diff
--- a/libinterp/corefcn/oct-tex-lexer.ll	Sun Aug 18 16:36:41 2013 -0400
+++ b/libinterp/corefcn/oct-tex-lexer.ll	Sun Aug 18 16:36:44 2013 -0400
@@ -106,6 +106,7 @@
 // Generic character
 %}
 
+"\n"	|
 .	{ yylval->ch = yytext[0]; return CH; }
 
 %%
--- a/libinterp/corefcn/txt-eng-ft.cc	Sun Aug 18 16:36:41 2013 -0400
+++ b/libinterp/corefcn/txt-eng-ft.cc	Sun Aug 18 16:36:44 2013 -0400
@@ -302,39 +302,141 @@
 // ---------------------------------------------------------------------------
 
 ft_render::ft_render (void)
-    : text_processor (), face (0), bbox (1, 4, 0.0),
-      xoffset (0), yoffset (0), multiline_halign (0),
-      multiline_align_xoffsets (), mode (MODE_BBOX),
-      red (0), green (0), blue (0)
+    : text_processor (), fonts (), bbox (1, 4, 0.0), halign (0), xoffset (0),
+      yoffset (0), mode (MODE_BBOX), red (0), green (0), blue (0)
 {
 }
 
 ft_render::~ft_render (void)
 {
-  if (face)
-    FT_Done_Face (face);
+  fonts.clear ();
 }
 
 void
 ft_render::set_font (const std::string& name, const std::string& weight,
                      const std::string& angle, double size)
 {
-  if (face)
-    FT_Done_Face (face);
+  if (fonts.size () > 1)
+    ::warning ("ft_render: resetting font parameters while the font stack "
+               "contains more than 1 element.");
+
+  // In all cases, we only replace the first/bottom font in the stack, if any.
+  // Calling this method while there's more than 1 font in the stack does
+  // not make sense: we're not gonna reconstruct the entire font stack.
+
+  if (fonts.size ())
+    fonts.pop_front ();
 
   // FIXME: take "fontunits" into account
-  face = ft_manager::get_font (name, weight, angle, size);
+  FT_Face face = ft_manager::get_font (name, weight, angle, size);
 
   if (face)
     {
       if (FT_Set_Char_Size (face, 0, size*64, 0, 0))
         ::warning ("ft_render: unable to set font size to %d", size);
+
+      fonts.push_front (ft_font (name, weight, angle, size, face));
     }
   else
     ::warning ("ft_render: unable to load appropriate font");
 }
 
 void
+ft_render::push_new_line (void)
+{
+  switch (mode)
+    {
+    case MODE_BBOX:
+        {
+          // Create a new bbox entry based on the current font.
+
+          FT_Face face = current_face ();
+
+          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);
+            }
+        }
+      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 = compute_line_xoffset (new_bbox);
+          yoffset += (old_bbox(1) - (new_bbox(1) + new_bbox(3)));
+        }
+      break;
+    }
+}
+
+int
+ft_render::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_render::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) = xmax (bbox(2), (*it)(2));
+            }
+        }
+      break;
+    }
+}
+
+void
 ft_render::set_mode (int m)
 {
   mode = m;
@@ -344,6 +446,8 @@
     case MODE_BBOX:
       xoffset = yoffset = 0;
       bbox = Matrix (1, 4, 0.0);
+      line_bbox.clear ();
+      push_new_line ();
       break;
     case MODE_RENDER:
       if (bbox.numel () != 4)
@@ -357,7 +461,7 @@
         {
           pixels = uint8NDArray (dim_vector (4, bbox(2), bbox(3)),
                                  static_cast<uint8_t> (0));
-          xoffset = 0;
+          xoffset = compute_line_xoffset (line_bbox.front ());
           yoffset = -bbox(1)-1;
         }
       break;
@@ -370,18 +474,13 @@
 void
 ft_render::visit (text_element_string& e)
 {
+  FT_Face face = current_face ();
+
   if (face)
     {
-      int line_index = 0;
-      FT_UInt box_line_width = 0;
       std::string str = e.string_value ();
       FT_UInt glyph_index, previous = 0;
 
-      if (mode == MODE_BBOX)
-        multiline_align_xoffsets.clear ();
-      else if (mode == MODE_RENDER)
-        xoffset += multiline_align_xoffsets[line_index];
-
       for (size_t i = 0; i < str.length (); i++)
         {
           glyph_index = FT_Get_Char_Index (face, str[i]);
@@ -397,17 +496,13 @@
                 case MODE_RENDER:
                   if (str[i] == '\n')
                     {
-                    glyph_index = FT_Get_Char_Index (face, ' ');
-                    if (!glyph_index || FT_Load_Glyph (face, glyph_index, FT_LOAD_DEFAULT))
-                      {
-                        gripe_missing_glyph (' ');
-                      }
-                    else
-                      {
-                        line_index++;
-                        xoffset = multiline_align_xoffsets[line_index];
-                        yoffset -= (face->size->metrics.height >> 6);
-                      }
+                      glyph_index = FT_Get_Char_Index (face, ' ');
+                      if (!glyph_index || FT_Load_Glyph (face, glyph_index, FT_LOAD_DEFAULT))
+                        {
+                          gripe_missing_glyph (' ');
+                        }
+                      else
+                        push_new_line ();
                     }
                   else if (FT_Render_Glyph (face->glyph, FT_RENDER_MODE_NORMAL))
                     {
@@ -443,7 +538,8 @@
                             if (x0+c < 0 || x0+c >= pixels.dim2 ()
                                 || y0-r < 0 || y0-r >= pixels.dim3 ())
                               {
-                                //::error ("out-of-bound indexing!!");
+                                //::warning ("ft_render: 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)
                               {
@@ -464,86 +560,42 @@
                       glyph_index = FT_Get_Char_Index (face, ' ');
                       if (! glyph_index
                           || FT_Load_Glyph (face, glyph_index, FT_LOAD_DEFAULT))
-                      {
-                        gripe_missing_glyph (' ');
-                      }
-                    else
-                      {
-                        multiline_align_xoffsets.push_back (box_line_width);
-                        // 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);
-                      }
+                        {
+                          gripe_missing_glyph (' ');
+                        }
+                      else
+                        push_new_line ();
                     }
                   else
                     {
-                    // width
-                    if (previous)
-                      {
-                        FT_Vector delta;
+                      Matrix& bb = line_bbox.back ();
 
-                        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;
+                      // 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;
 
-                    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;
-                      }
+                          FT_Get_Kerning (face, previous, glyph_index,
+                                          FT_KERNING_DEFAULT, &delta);
 
-                    asc = yoffset + (asc >> 6);
-                    desc = yoffset + (desc >> 6);
+                          bb(2) += (delta.x >> 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;
-                  }
+                      // Extend current line bounding box by the width of the
+                      // current glyph.
+                      bb(2) += (face->glyph->advance.x >> 6);
+                    }
                   break;
                 }
+
                 if (str[i] == '\n')
                   previous = 0;
                 else
                   previous = glyph_index;
             }
         }
-      if (mode == MODE_BBOX)
-        {
-          /* Push last the width associated with the last line */
-          multiline_align_xoffsets.push_back (box_line_width);
-
-          for (unsigned int i = 0; i < multiline_align_xoffsets.size (); i++)
-            {
-            /* Center align */
-            if (multiline_halign == 1)
-              multiline_align_xoffsets[i] = (bbox(2) - multiline_align_xoffsets[i])/2;
-            /* Right align */
-            else if (multiline_halign == 2)
-              multiline_align_xoffsets[i] = (bbox(2) - multiline_align_xoffsets[i]);
-            /* Left align */
-            else
-              multiline_align_xoffsets[i] = 0;
-            }
-        }
     }
 }
 
@@ -572,6 +624,7 @@
 {
   set_mode (MODE_BBOX);
   elt->accept (*this);
+  compute_bbox ();
   box = bbox;
 
   set_mode (MODE_RENDER);
@@ -638,6 +691,7 @@
 {
   set_mode (MODE_BBOX);
   elt->accept (*this);
+  compute_bbox ();
 
   Matrix extent (1, 2, 0.0);
 
@@ -686,13 +740,13 @@
 void
 ft_render::text_to_pixels (const std::string& txt,
                            uint8NDArray& pixels_, Matrix& box,
-                           int halign, int valign, double rotation,
+                           int _halign, int valign, double rotation,
                            const caseless_str& interpreter)
 {
   // FIXME: clip "rotation" between 0 and 360
   int rot_mode = rotation_to_mode (rotation);
 
-  multiline_halign = halign;
+  halign = _halign;
 
   text_element *elt = text_parser::parse (txt, interpreter);
   pixels_ = render (elt, box, rot_mode);
--- a/libinterp/corefcn/txt-eng-ft.h	Sun Aug 18 16:36:41 2013 -0400
+++ b/libinterp/corefcn/txt-eng-ft.h	Sun Aug 18 16:36:44 2013 -0400
@@ -25,6 +25,7 @@
 
 #if HAVE_FREETYPE
 
+#include <list>
 #include <vector>
 
 #include <ft2build.h>
@@ -92,15 +93,97 @@
 
   ft_render& operator = (const ft_render&);
 
+  // Class to hold information about fonts and a strong
+  // reference to the font objects loaded by freetype.
+  class ft_font
+    {
+    public:
+      std::string name;
+      std::string weight;
+      std::string angle;
+      double size;
+      FT_Face face;
+
+      ft_font (const std::string& nm, const std::string& wt,
+               const std::string& ang, double sz, FT_Face f)
+        : name (nm), weight (wt), angle (ang), size (sz), face (f) { }
+
+      ft_font (const ft_font& ft)
+        : name (ft.name), weight (ft.weight), angle (ft.angle), size (ft.size)
+        {
+          if (FT_Reference_Face (ft.face) == 0)
+            face = ft.face;
+        }
+
+      ~ft_font (void)
+        {
+          if (face)
+            FT_Done_Face (face);
+        }
+
+      ft_font& operator = (const ft_font& ft)
+        {
+          if (&ft != this)
+            {
+              name = ft.name;
+              weight = ft.weight;
+              angle = ft.angle;
+              size = ft.size;
+              FT_Done_Face (face);
+              if (FT_Reference_Face (ft.face))
+                face = ft.face;
+              else
+                face = 0;
+            }
+
+          return *this;
+        }
+
+    private:
+      ft_font (void);
+    };
+
+  FT_Face current_face (void)
+    { return fonts.size () ? fonts.back ().face : 0; }
+
+  void push_new_line (void);
+
+  void compute_bbox (void);
+
+  int compute_line_xoffset (const Matrix& lb) const;
+
 private:
-  FT_Face face;
+  // The stack of fonts currently used by the renderer.
+  std::list<ft_font> fonts;
+
+  // 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 current X offset for the next glyph.
   int xoffset;
+
+  // The current Y offset for the next glyph.
   int yoffset;
-  int multiline_halign;
-  std::vector<int> multiline_align_xoffsets;
+
+  // The current mode of the rendering process (box computing or rendering).
   int mode;
+
+  // The base color of the rendered text.
   uint8_t red, green, blue;
 };