# HG changeset patch
# User Pantxo Diribarne <pantxo.diribarne@gmail.com>
# Date 1615490739 -3600
#      Thu Mar 11 20:25:39 2021 +0100
# Node ID fd56f362c5797be932c3a13580d9196578865559
# Parent  44abfdbccb9c19aa0f2f463709f5f62bf6d8a252
Allow arbitrary text rotation onscreen (bug #33118)

* gl-render.{h,cc} (opengl-renderer::draw_texture_image): New private method
  with code extracted
  from draw_image.
  (opengl-renderer::render_text (uint8NDArray ...)): New methods to prepare
  text rendering and finally call draw_texture_image. Let the existing
  render_text(std::string,...) call this one after preparing the pixels array
  using text2pixels.
  (opengl-renderer::set_ortho_coordinates): New method to setup orthogonal
  transform.
  (opengl-renderer::restore_previous_coordinates): New method to restore
  previous transform.
  (opengl-renderer::draw_text): Handle clipping manually for the text as well.
  (opengl_texture::create): Add new case for uint8 RGBA image data.

* graphics.{in.h,cc} (text::properties::get_extent_matrix): Add a bool argument
  "rotated" that defaults to "false". Re-compute the extent matrix if necessary.
  (text::properties::get_extent): Make use of get_extent_matrix. 
  (text::properties::update_text_extent): Always call text2pixels with 0 degrees
  rotation.

* __print_parse_opts__.m: By default, also use the opengl renderer for raster
  images that include rotated text.

diff -r 44abfdbccb9c -r fd56f362c579 libinterp/corefcn/gl-render.cc
--- a/libinterp/corefcn/gl-render.cc	Thu Mar 11 14:41:30 2021 +0100
+++ b/libinterp/corefcn/gl-render.cc	Thu Mar 11 20:25:39 2021 +0100
@@ -182,7 +182,7 @@
     dim_vector dv (data.dims ());
 
     // Expect RGB data
-    if (dv.ndims () == 3 && dv(2) == 3)
+    if (dv.ndims () == 3 && (dv(2) == 3 || dv(2) == 4))
       {
         // FIXME: dim_vectors hold octave_idx_type values.
         //        Should we check for dimensions larger than intmax?
@@ -274,7 +274,7 @@
             glfcns.glTexImage2D (GL_TEXTURE_2D, 0, 3, tw, th, 0,
                                  GL_RGB, GL_UNSIGNED_SHORT, a);
           }
-        else if (data.is_uint8_type ())
+        else if (data.is_uint8_type () && dv(2) == 3)
           {
             const uint8NDArray xdata = data.uint8_array_value ();
 
@@ -293,6 +293,26 @@
             glfcns.glTexImage2D (GL_TEXTURE_2D, 0, 3, tw, th, 0,
                                  GL_RGB, GL_UNSIGNED_BYTE, a);
           }
+        else if (data.is_uint8_type () && dv(2) == 4)
+          {
+            const uint8NDArray xdata = data.uint8_array_value ();
+
+            OCTAVE_LOCAL_BUFFER (GLubyte, a, (4*tw*th));
+
+            for (int i = 0; i < h; i++)
+              {
+                for (int j = 0, idx = i*tw*4; j < w; j++, idx += 4)
+                  {
+                    a[idx]   = xdata(i,j,0);
+                    a[idx+1] = xdata(i,j,1);
+                    a[idx+2] = xdata(i,j,2);
+                    a[idx+3] = xdata(i,j,3);
+                  }
+              }
+
+            glfcns.glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, tw, th, 0,
+                                 GL_RGBA, GL_UNSIGNED_BYTE, a);
+          }
         else
           {
             ok = false;
@@ -578,8 +598,6 @@
 
     void combine (GLdouble xyz[3], void *data[4], GLfloat w[4], void **out_data)
     {
-      //printf ("patch_tessellator::combine\n");
-
       vertex_data::vertex_data_rep *v[4];
       int vmax = 4;
 
@@ -3839,6 +3857,52 @@
   }
 
   void
+  opengl_renderer::set_ortho_coordinates (void)
+  {
+#if defined (HAVE_OPENGL)
+
+    m_glfcns.glMatrixMode (GL_PROJECTION);
+    m_glfcns.glPushMatrix ();
+    m_glfcns.glLoadIdentity ();
+
+    Matrix vp = get_viewport_scaled ();
+    m_glfcns.glOrtho (0, vp(2), vp(3), 0, xZ1, xZ2);
+    m_glfcns.glMatrixMode (GL_MODELVIEW);
+    m_glfcns.glPushMatrix ();
+    m_glfcns.glLoadIdentity ();
+
+#else
+
+    // This shouldn't happen because construction of opengl_renderer
+    // objects is supposed to be impossible if OpenGL is not available.
+
+    panic_impossible ();
+
+#endif
+  }
+
+  void
+  opengl_renderer::restore_previous_coordinates (void)
+  {
+#if defined (HAVE_OPENGL)
+
+    // Restore previous coordinate system
+    m_glfcns.glMatrixMode (GL_MODELVIEW);
+    m_glfcns.glPopMatrix();
+    m_glfcns.glMatrixMode (GL_PROJECTION);
+    m_glfcns.glPopMatrix();
+
+#else
+
+    // This shouldn't happen because construction of opengl_renderer
+    // objects is supposed to be impossible if OpenGL is not available.
+
+    panic_impossible ();
+
+#endif
+  }
+
+  void
   opengl_renderer::draw_text (const text::properties& props)
   {
 #if defined (HAVE_OPENGL)
@@ -3848,31 +3912,22 @@
 
     Matrix pos = xform.scale (props.get_data_position ());
 
-    // Handle clipping manually when drawing text background
+    // Handle clipping manually when drawing text in ortho coordinates
     if (! props.is_clipping ()
         || (clip_code (pos(0), pos(1), pos.numel () > 2 ? pos(2) : 0.0) == 0x40))
       {
         set_clipping (false);
+
         draw_text_background (props);
+
+        set_font (props);
+
+        render_text (props.get_pixels (), props.get_extent_matrix (),
+                     pos(0), pos(1), pos(2), props.get_rotation ());
+
         set_clipping (props.is_clipping ());
       }
 
-    set_font (props);
-
-    const Matrix bbox = props.get_extent_matrix ();
-
-    bool blend = m_glfcns.glIsEnabled (GL_BLEND);
-
-    m_glfcns.glEnable (GL_BLEND);
-    m_glfcns.glEnable (GL_ALPHA_TEST);
-    m_glfcns.glRasterPos3d (pos(0), pos(1), pos.numel () > 2 ? pos(2) : 0.0);
-    m_glfcns.glBitmap (0, 0, 0, 0, bbox(0), bbox(1), nullptr);
-    m_glfcns.glDrawPixels (bbox(2), bbox(3), GL_RGBA, GL_UNSIGNED_BYTE,
-                           props.get_pixels ().data ());
-    m_glfcns.glDisable (GL_ALPHA_TEST);
-    if (! blend)
-      m_glfcns.glDisable (GL_BLEND);
-
 #else
 
     octave_unused_parameter (props);
@@ -3887,7 +3942,7 @@
 
   void
   opengl_renderer::draw_text_background (const text::properties& props,
-                                         bool do_rotate)
+                                         bool /*do_rotate*/)
   {
 #if defined (HAVE_OPENGL)
 
@@ -3902,15 +3957,7 @@
                                                       pos(2), true);
 
     // Save current transform matrices and set orthogonal window coordinates
-    m_glfcns.glMatrixMode (GL_PROJECTION);
-    m_glfcns.glPushMatrix ();
-    m_glfcns.glLoadIdentity ();
-
-    Matrix vp = get_viewport_scaled ();
-    m_glfcns.glOrtho (0, vp(2), vp(3), 0, xZ1, xZ2);
-    m_glfcns.glMatrixMode (GL_MODELVIEW);
-    m_glfcns.glPushMatrix ();
-    m_glfcns.glLoadIdentity ();
+    set_ortho_coordinates ();
 
     // Translate coordinates so that the text anchor is (0,0)
     m_glfcns.glTranslated (pixpos(0), pixpos(1), -pixpos(2));
@@ -3919,9 +3966,7 @@
     //        Handle others here.
     double rotation = props.get_rotation ();
 
-    if (do_rotate && rotation != 0.0 && rotation != 90.0
-        && rotation != 180.0 && rotation != 270.0)
-      m_glfcns.glRotated (-rotation, 0.0, 0.0, 1.0);
+    m_glfcns.glRotated (-rotation, 0.0, 0.0, 1.0);
 
     double m = points_to_pixels (props.get_margin ());
     const Matrix bbox = props.get_extent_matrix ();
@@ -3967,10 +4012,7 @@
         set_linestyle ("-");
       }
 
-    // Restore previous coordinate system
-    m_glfcns.glPopMatrix();
-    m_glfcns.glMatrixMode (GL_PROJECTION);
-    m_glfcns.glPopMatrix();
+    restore_previous_coordinates ();
 
 #else
 
@@ -3991,12 +4033,34 @@
 #if defined (HAVE_OPENGL)
 
     octave_value cdata = props.get_color_data ();
+    Matrix x = props.get_xdata ().matrix_value ();
+    Matrix y = props.get_ydata ().matrix_value ();
+
+    draw_texture_image (cdata, x, y);
+
+#else
+
+    octave_unused_parameter (props);
+
+    // This shouldn't happen because construction of opengl_renderer
+    // objects is supposed to be impossible if OpenGL is not available.
+
+    panic_impossible ();
+
+#endif
+  }
+
+  void
+  opengl_renderer::draw_texture_image (const octave_value cdata, Matrix x,
+                                       Matrix y, bool ortho)
+  {
+#if defined (HAVE_OPENGL)
+
     dim_vector dv (cdata.dims ());
     int h = dv(0);
     int w = dv(1);
     double x0, x1, y0, y1;
 
-    Matrix x = props.get_xdata ().matrix_value ();
     double dx = 1.0;
     if (w > 1)
       dx = (x(1) - x(0)) / (w - 1);
@@ -4004,7 +4068,6 @@
     x0 = x(0)-dx/2;
     x1 = x(1)+dx/2;
 
-    Matrix y = props.get_ydata ().matrix_value ();
     double dy = 1.0;
     if (h > 1)
       dy = (y(1) - y(0)) / (h - 1);
@@ -4013,7 +4076,7 @@
     y1 = y(1)+dy/2;
 
     // Expect RGB data
-    if (dv.ndims () == 3 && dv(2) == 3)
+    if (dv.ndims () == 3 && (dv(2) == 3 || dv(2) == 4))
       {
         opengl_texture tex  = opengl_texture::create (m_glfcns, cdata);
         if (tex.is_valid ())
@@ -4025,16 +4088,28 @@
             m_glfcns.glBegin (GL_QUADS);
 
             tex.tex_coord (0.0, 0.0);
-            m_glfcns.glVertex3d (x0, y0, 0.0);
+            if (ortho)
+              m_glfcns.glVertex2d (x0, y0);
+            else
+              m_glfcns.glVertex3d (x0, y0, 0.0);
 
             tex.tex_coord (1.0, 0.0);
-            m_glfcns.glVertex3d (x1, y0, 0.0);
+            if (ortho)
+              m_glfcns.glVertex2d (x1, y0);
+            else
+              m_glfcns.glVertex3d (x1, y0, 0.0);
 
             tex.tex_coord (1.0, 1.0);
-            m_glfcns.glVertex3d (x1, y1, 0.0);
+            if (ortho)
+              m_glfcns.glVertex2d (x1, y1);
+            else
+              m_glfcns.glVertex3d (x1, y1, 0.0);
 
             tex.tex_coord (0.0, 1.0);
-            m_glfcns.glVertex3d (x0, y1, 0.0);
+            if (ortho)
+              m_glfcns.glVertex2d (x0, y1);
+            else
+              m_glfcns.glVertex3d (x0, y1, 0.0);
 
             m_glfcns.glEnd ();
             m_glfcns.glDisable (GL_TEXTURE_2D);
@@ -4045,7 +4120,10 @@
 
 #else
 
-    octave_unused_parameter (props);
+    octave_unused_parameter (cdata);
+    octave_unused_parameter (x);
+    octave_unused_parameter (y);
+    octave_unused_parameter (ortho);
 
     // This shouldn't happen because construction of opengl_renderer
     // objects is supposed to be impossible if OpenGL is not available.
@@ -4795,18 +4873,7 @@
         uint8NDArray pixels;
         text_to_pixels (txt, pixels, bbox, halign, valign, rotation);
 
-        bool blend = m_glfcns.glIsEnabled (GL_BLEND);
-
-        m_glfcns.glEnable (GL_BLEND);
-        m_glfcns.glEnable (GL_ALPHA_TEST);
-        m_glfcns.glRasterPos3d (x, y, z);
-        m_glfcns.glBitmap(0, 0, 0, 0, bbox(0), bbox(1), nullptr);
-        m_glfcns.glDrawPixels (bbox(2), bbox(3),
-                               GL_RGBA, GL_UNSIGNED_BYTE, pixels.data ());
-        m_glfcns.glDisable (GL_ALPHA_TEST);
-
-        if (! blend)
-          m_glfcns.glDisable (GL_BLEND);
+        render_text (pixels, bbox, x, y, z, rotation);
       }
 
     return bbox;
@@ -4828,4 +4895,63 @@
 
 #endif
   }
+
+  void
+  opengl_renderer::render_text (uint8NDArray pixels, Matrix bbox,
+                                double x, double y, double z, double rotation)
+  {
+#if defined (HAVE_OPENGL)
+
+    // Transform data coordinates to screen pixel ortho coordinates
+    ColumnVector pixpos = get_transform ().transform (x, y, z, false);
+    Matrix xdata(1, 2, bbox(0) / m_devpixratio);
+    xdata(1) += (bbox(2) - 1) / m_devpixratio;
+    Matrix ydata(1, 2, -bbox(1) / m_devpixratio);
+    ydata(1) -= (bbox(3) - 1) / m_devpixratio;
+
+
+
+    bool blend = m_glfcns.glIsEnabled (GL_BLEND);
+    m_glfcns.glEnable (GL_BLEND);
+    m_glfcns.glEnable (GL_ALPHA_TEST);
+
+
+    set_ortho_coordinates ();
+
+    // Translate coordinates so that the text anchor is (0,0)
+    m_glfcns.glTranslated (pixpos(0), pixpos(1), -pixpos(2));
+
+    m_glfcns.glRotated (-rotation, 0.0, 0.0, 1.0);
+
+    // Permute pixels returned by freetype
+    Array<octave_idx_type> perm (dim_vector (3, 1));
+    perm(0) = 2;
+    perm(1) = 1;
+    perm(2) = 0;
+    draw_texture_image (pixels.permute (perm),
+                        xdata, ydata, true);
+
+    restore_previous_coordinates ();
+
+    m_glfcns.glDisable (GL_ALPHA_TEST);
+
+    if (! blend)
+      m_glfcns.glDisable (GL_BLEND);
+
+#else
+
+    octave_unused_parameter (pixels);
+    octave_unused_parameter (bbox);
+    octave_unused_parameter (x);
+    octave_unused_parameter (y);
+    octave_unused_parameter (z);
+    octave_unused_parameter (rotation);
+
+    // This shouldn't happen because construction of opengl_renderer
+    // objects is supposed to be impossible if OpenGL is not available.
+
+    panic_impossible ();
+
+#endif
+  }
 }
diff -r 44abfdbccb9c -r fd56f362c579 libinterp/corefcn/gl-render.h
--- a/libinterp/corefcn/gl-render.h	Thu Mar 11 14:41:30 2021 +0100
+++ b/libinterp/corefcn/gl-render.h	Thu Mar 11 20:25:39 2021 +0100
@@ -183,8 +183,15 @@
               | (z > zmax ? 1 : 0) << 5
               | (is_nan_or_inf (x, y, z) ? 0 : 1) << 6);
     }
+    
+    void render_text (uint8NDArray pixels, Matrix bbox,
+                      double x, double y, double z, double rotation);
+    
+    void set_normal (int bfl_mode, const NDArray& n, int j, int i);
 
-    void set_normal (int bfl_mode, const NDArray& n, int j, int i);
+    void set_ortho_coordinates (void);
+
+    void restore_previous_coordinates (void);
 
     double points_to_pixels (const double val) const;
 
@@ -204,6 +211,9 @@
     void draw_all_lights (const base_properties& props,
                           std::list<graphics_object>& obj_list);
 
+    void draw_texture_image (const octave_value cdata,
+                             Matrix x, Matrix y, bool ortho = false);
+
   protected:
 
     opengl_functions& m_glfcns;
diff -r 44abfdbccb9c -r fd56f362c579 libinterp/corefcn/graphics.cc
--- a/libinterp/corefcn/graphics.cc	Thu Mar 11 14:41:30 2021 +0100
+++ b/libinterp/corefcn/graphics.cc	Thu Mar 11 20:25:39 2021 +0100
@@ -7128,7 +7128,7 @@
             }
           else
             {
-              Matrix text_ext = text_props.get_extent_matrix ();
+              Matrix text_ext = text_props.get_extent_matrix (true);
 
               // The text extent is returned in device pixels.  Unscale and
               // work with logical pixels
@@ -9404,10 +9404,47 @@
 }
 
 Matrix
-text::properties::get_extent_matrix (void) const
+text::properties::get_extent_matrix (bool rotated) const
 {
   // FIXME: Should this function also add the (x,y) base position?
-  return extent.get ().matrix_value ();
+  Matrix ext = extent.get ().matrix_value ();
+
+  if (rotated && get_rotation () != 0)
+    {
+      double rot = get_rotation () * 4.0 * atan (1.0) / 180;
+      double x0 = ext(0) * cos (rot) - ext(1) * sin (rot);
+      double x1 = x0;
+      double y0 = ext(0) * sin (rot) + ext(1) * cos (rot);
+      double y1 = y0;
+
+      double tmp = (ext(0)+ext(2)) * cos (rot) - ext(1) * sin (rot);
+      x0 = std::min (x0, tmp);
+      x1 = std::max (x1, tmp);
+      tmp = (ext(0)+ext(2)) * sin (rot) + ext(1) * cos (rot);
+      y0 = std::min (y0, tmp);
+      y1 = std::max (y1, tmp);
+
+      tmp = (ext(0)+ext(2)) * cos (rot) - (ext(1)+ext(3)) * sin (rot);
+      x0 = std::min (x0, tmp);
+      x1 = std::max (x1, tmp);
+      tmp = (ext(0)+ext(2)) * sin (rot) + (ext(1)+ext(3)) * cos (rot);
+      y0 = std::min (y0, tmp);
+      y1 = std::max (y1, tmp);
+
+      tmp = ext(0) * cos (rot) - (ext(1)+ext(3)) * sin (rot);
+      x0 = std::min (x0, tmp);
+      x1 = std::max (x1, tmp);
+      tmp = ext(0) * sin (rot) + (ext(1)+ext(3)) * cos (rot);
+      y0 = std::min (y0, tmp);
+      y1 = std::max (y1, tmp);
+
+      ext(0) = x0;
+      ext(1) = y0;
+      ext(2) = x1 - x0;
+      ext(3) = y1 - y0;
+    }
+  
+  return ext;
 }
 
 octave_value
@@ -9415,7 +9452,7 @@
 {
   // FIXME: This doesn't work right for 3D plots.
   // (It doesn't in Matlab either, at least not in version 6.5.)
-  Matrix m = extent.get ().matrix_value ();
+  Matrix m = get_extent_matrix (true);
   Matrix pos = get_position ().matrix_value ();
   Matrix p = convert_text_position (pos, *this, get_units (), "pixels");
 
@@ -9525,7 +9562,7 @@
   octave::autolock guard (gh_mgr.graphics_lock ());
 
   txt_renderer.text_to_pixels (sv.join ("\n"), pixels, bbox,
-                               halign, valign, get_rotation (),
+                               halign, valign, 0.0,
                                get_interpreter ());
   // The bbox is relative to the text's position.  We'll leave it that
   // way, because get_position does not return valid results when the
diff -r 44abfdbccb9c -r fd56f362c579 libinterp/corefcn/graphics.in.h
--- a/libinterp/corefcn/graphics.in.h	Thu Mar 11 14:41:30 2021 +0100
+++ b/libinterp/corefcn/graphics.in.h	Thu Mar 11 20:25:39 2021 +0100
@@ -4528,7 +4528,7 @@
     END_PROPERTIES
 
     OCTINTERP_API Matrix get_data_position (void) const;
-    OCTINTERP_API Matrix get_extent_matrix (void) const;
+    OCTINTERP_API Matrix get_extent_matrix (bool rotated = false) const;
     const uint8NDArray& get_pixels (void) const { return pixels; }
 
     // Text renderer, used for calculation of text size
diff -r 44abfdbccb9c -r fd56f362c579 scripts/plot/util/private/__print_parse_opts__.m
--- a/scripts/plot/util/private/__print_parse_opts__.m	Thu Mar 11 14:41:30 2021 +0100
+++ b/scripts/plot/util/private/__print_parse_opts__.m	Thu Mar 11 20:25:39 2021 +0100
@@ -248,18 +248,7 @@
 
   if (strcmp (arg_st.renderer, "auto"))
     if (opengl_ok && strcmp (graphics_toolkit (arg_st.figure), "qt"))
-      ## "opengl" renderer only does text rotations of 0°, 90°, 180°, 270°, ...
-      ht = findall (arg_st.figure, "type", "text");
-      if (isempty (ht))
-        angles = [];
-      else
-        angles = [get(ht, "rotation"){:}];
-      endif
-      if (any (mod (angles, 90)))
-        arg_st.renderer = "painters";
-      else
-        arg_st.renderer = "opengl";
-      endif
+      arg_st.renderer = "opengl";
     else
       arg_st.renderer = "painters";
     endif
