exporting patch:
# HG changeset patch
# User Daniel J Sebald <daniel.sebald@ieee.org>
# Date 1517209305 21600
#      Mon Jan 29 01:01:45 2018 -0600
# Node ID 197e49c1164f230deb5853bb4cb4b9de7cf8d602
# Parent  41a87a2a8d381393ada16ebdef0907e57ba1ef24
OpenGL viewport setting and non-position-property gl2ps_print context (bug #52940)

* graphics.in.h (graphics_xform): Associate the x_gl_mat1 and x_gl_mat2
  variables with graphics_xform object rather than the axes_properties object.
  (graphics_xform::graphics_xform): Provide initializers for x_gl_mat1 and
  x_gl_mat2 for all three constructors.
  (graphics_xform::get_transform_matrix): Member functiom moved from
  axes::properties.
  (graphics_xform::get_inverse_transform_matrix): Ditto.
  (graphics_xform::get_opengl_matrix_1): Ditto.
  (graphics_xform::get_opengl_matrix_2): Ditto.
  (graphics_xform::get_transform_zlim): Ditto.
  (graphics_xform::pixel2coord): Ditto.
  (graphics_xform::coord2pixel): Ditto.
  (axes::properties): Move all the functions listed above to graphics_xform.
  Add a new function declaration get_transform().  Move x_gl_mat1 and
  x_gl_mat2 to graphics_xform and discard variables x_render, x_render_inv
  and x_zlim.  Discard the hidden properties x_viewtransform,
  x_projectiontransform, x_viewporttransform, x_normrendertransform,
  x_rendertransform that appear to be no long in use anywhere in the project.

* graphics.cc (axes::properties::init): Remove x_zlim, no longer within this
  object.
  (axes::properties::update_camera): Remove x_normrender, not needed for this
  function.  Do not set x_render or x_render_inv as these are no longer part
  of this object.  Break the last half of this function that deals with
  creating the tranform into a new function get_transform().
  (axes::properties::get_transform): Added.  New function comprised of the
  second half of update_camera.  Constructs the transform needed for rendering
  and puts it into the form of a graphics_xform object.  Note, no longer set
  the local variables x_viewtransform, x_projectiontransform,
  x_viewporttransform, x_normrendertransform, x_rendertransform.
  (axes::properties::update_title_position): Do not use x_zlim directly to
  compute xform_vector, instead set the third term to 0 then call
  xform.pixel2coord instead of xform.untransform since pixel2coord constructs
  that third term from x_zlim in an identical way.

* Backend.cc (Backend::print_figure): Remove condition on 'visible' property
  so that printing is always done.

* Canvas.cc (Canvas::updateCurrentPoint): Get x_zlim matrix from xform after
  the latter has been retrieved from the axes properties.
  (Canvas::updateCurrentPoint): Ditto.
  (Canvas::select_object): Call axes properties member function get_transform()
  prior to calling pixel2coord().
  (Canvas::canvasMouseMoveEvent): Ditto.
  (Canvas::canvasMouseReleaseEvent): Ditto.

* gl-select.cc (opengl_selector::fake_text): Use the xform object from the
  parent class to get the transform rather than calling get_transform().

* gl-render.h (opengl_renderer): Remove member function get_transform().
  Change xform member variable from private to protected.  Add new protected
  member variable matrix parent_pix_size.

* gl-render.cc (opengl_renderer::opengl_renderer): Add initializers for new
  xform and parent_pix_size variables.
  (opengl_renderer::setup_opengl_transformation): Move the storage of xform
  to the start of the function then use it to retrieve x_zlim, x_gl_mat1 and
  x_gl_mat2.  Note, parent_pix_size is passed into get_transform() to get
  transform appropriate for on-screen or print mode.
  (opengl_renderer::draw_text_background): Use xform member function
  transform() rather than call get_transform().

* gl2ps-print.cc (gl2ps_renderer::draw): Get the figure size (in pixels) from
  hidden property __guidata__, with sanity checks, then put that information
  in parent_pix_size matrix (which is passed into the axes properties by the
  base class).
  (gl2ps_renderer::fix_strlist_position): Use the xform object rather than
  member function get_transform to get coord_pix.
  (gl2ps_renderer::strlist_to_svg): Ditto.
  (gl2ps_renderer::draw_text): Ditto.
  (gl2ps_print): Get the figure size (in pixels) from hidden property
  __guidata__, with sanity checks, then use the size to set OpenGL viewport.

* __init_fltk__.cc (plot_window::pixel2pos): Call axes properties member
  function get_transform() prior to calling pixel2coord().
  (plot_window::set_axes_currentpoint): Call axes properties member
  function get_transform() to fetch xform then use the latter to get x_zlim
  and compute the inverse transform.

* print.m: No longer set "units" figure property to "pixels" and restore later.
  No longer set the "position" property to the canvas size (in pixels), but
  instead put that information in the __guidata__ hidden property as structure
  member.  Add comment about "position" also shouldn't need to be saved and
  restored, but FLTK malfunctions if this property isn't restored.

* __opengl_print__.m: Restore the drawnow() at front of the routine taken out
  by changeset 24638:a3031d4967ce.  Allow the qt toolkit to do non-visible
  printing rather than use OSMesa.

diff --git a/libgui/graphics/Backend.cc b/libgui/graphics/Backend.cc
--- a/libgui/graphics/Backend.cc
+++ b/libgui/graphics/Backend.cc
@@ -185,14 +185,11 @@ namespace QtHandles
                          const std::string& file_cmd,
                          const std::string& /*debug_file*/) const
   {
-    if (go.get_properties ().is_visible ())
-      {
-        ObjectProxy *proxy = toolkitObjectProxy (go);
+    ObjectProxy *proxy = toolkitObjectProxy (go);
 
-        if (proxy)
-          proxy->print (QString::fromStdString (file_cmd),
+    if (proxy)
+      proxy->print (QString::fromStdString (file_cmd),
                         QString::fromStdString (term));
-      }
   }
 
   uint8NDArray
diff --git a/libgui/graphics/Canvas.cc b/libgui/graphics/Canvas.cc
--- a/libgui/graphics/Canvas.cc
+++ b/libgui/graphics/Canvas.cc
@@ -139,8 +139,8 @@ namespace QtHandles
         if (childObj.isa ("axes"))
           {
             axes::properties& ap = Utils::properties<axes> (childObj);
-            Matrix x_zlim = ap.get_transform_zlim ();
             graphics_xform x_form = ap.get_transform ();
+            Matrix x_zlim = x_form.get_transform_zlim ();
 
             ColumnVector p1 = x_form.untransform (event->x (), event->y (),
                                                   x_zlim(0));
@@ -181,8 +181,8 @@ namespace QtHandles
             QWidget *w = qWidget ();
             QPoint p = w->mapFromGlobal (QCursor::pos ());
             axes::properties& ap = Utils::properties<axes> (childObj);
-            Matrix x_zlim = ap.get_transform_zlim ();
             graphics_xform x_form = ap.get_transform ();
+            Matrix x_zlim = x_form.get_transform_zlim ();
 
             ColumnVector p1 = x_form.untransform (p.x (), p.y (), x_zlim(0));
             ColumnVector p2 = x_form.untransform (p.x (), p.y (), x_zlim(1));
@@ -427,7 +427,7 @@ namespace QtHandles
             const axes::properties& ap =
               dynamic_cast<const axes::properties&> ((*it).get_properties ());
 
-            ColumnVector p0 = ap.pixel2coord (pt.x (), pt.y ());
+            ColumnVector p0 = ap.get_transform ().pixel2coord (pt.x (), pt.y ());
             Matrix xlim = ap.get_xlim ().matrix_value ();
             Matrix ylim = ap.get_ylim ().matrix_value ();
 
@@ -512,9 +512,9 @@ namespace QtHandles
 
               std::string mode = pan_mode (figObj);
 
-              ColumnVector p0 = ap.pixel2coord (m_mouseCurrent.x (),
+              ColumnVector p0 = ap.get_transform ().pixel2coord (m_mouseCurrent.x (),
                                                 m_mouseCurrent.y ());
-              ColumnVector p1 = ap.pixel2coord (event->x (),
+              ColumnVector p1 = ap.get_transform ().pixel2coord (event->x (),
                                                 event->y ());
 
               ap.translate_view (mode, p0(0), p1(0), p0(1), p1(1));
@@ -567,7 +567,7 @@ namespace QtHandles
             axes::properties& ap = Utils::properties<axes> (axesObj);
 
             if (fig)
-              fig->updateStatusBar (ap.pixel2coord (event->x (), event->y ()));
+              fig->updateStatusBar (ap.get_transform ().pixel2coord (event->x (), event->y ()));
           }
       }
   }
@@ -833,15 +833,15 @@ namespace QtHandles
               {
                 double factor = (m_clickMode ? 2.0 : 0.5);
 
-                ColumnVector p1 = ap.pixel2coord (event->x (), event->y ());
+                ColumnVector p1 = ap.get_transform ().pixel2coord (event->x (), event->y ());
 
                 ap.zoom_about_point (zm, p1(0), p1(1), factor);
               }
             else if (m_mouseMode == ZoomInMode)
               {
-                ColumnVector p0 = ap.pixel2coord (m_mouseAnchor.x (),
+                ColumnVector p0 = ap.get_transform ().pixel2coord (m_mouseAnchor.x (),
                                                   m_mouseAnchor.y ());
-                ColumnVector p1 = ap.pixel2coord (event->x (),
+                ColumnVector p1 = ap.get_transform ().pixel2coord (event->x (),
                                                   event->y ());
 
                 Matrix xl (1, 2, 0.0);
diff --git a/libgui/graphics/gl-select.cc b/libgui/graphics/gl-select.cc
--- a/libgui/graphics/gl-select.cc
+++ b/libgui/graphics/gl-select.cc
@@ -152,7 +152,7 @@ namespace octave
   {
     ColumnVector xpos, xp1, xp2;
 
-    xpos = get_transform ().transform (x, y, z, use_scale);
+    xpos = xform.transform (x, y, z, use_scale);
 
     xp1 = xp2 = xpos;
     xp1(0) += bbox(0);
@@ -162,10 +162,10 @@ namespace octave
 
     ColumnVector p1, p2, p3, p4;
 
-    p1 = get_transform ().untransform (xp1(0), xp1(1), xp1(2), false);
-    p2 = get_transform ().untransform (xp2(0), xp1(1), xp1(2), false);
-    p3 = get_transform ().untransform (xp2(0), xp2(1), xp1(2), false);
-    p4 = get_transform ().untransform (xp1(0), xp2(1), xp1(2), false);
+    p1 = xform.untransform (xp1(0), xp1(1), xp1(2), false);
+    p2 = xform.untransform (xp2(0), xp1(1), xp1(2), false);
+    p3 = xform.untransform (xp2(0), xp2(1), xp1(2), false);
+    p4 = xform.untransform (xp1(0), xp2(1), xp1(2), false);
 
     glBegin (GL_QUADS);
     glVertex3dv (p1.data ());
diff --git a/libinterp/corefcn/gl-render.cc b/libinterp/corefcn/gl-render.cc
--- a/libinterp/corefcn/gl-render.cc
+++ b/libinterp/corefcn/gl-render.cc
@@ -623,10 +623,10 @@ get_maxlights (void)
 namespace octave
 {
   opengl_renderer::opengl_renderer (void)
-    : toolkit (), xform (), xmin (), xmax (), ymin (), ymax (),
-      zmin (), zmax (), xZ1 (), xZ2 (), marker_id (), filled_marker_id (),
-      camera_pos (), camera_dir (), interpreter ("none"), txt_renderer (),
-      selecting (false)
+    : xform (), parent_pix_size (Matrix ()), toolkit (), xmin (), xmax (),
+      ymin (), ymax (), zmin (), zmax (), xZ1 (), xZ2 (), marker_id (),
+      filled_marker_id (), camera_pos (), camera_dir (), interpreter ("none"),
+      txt_renderer (), selecting (false)
   {
     // This constructor will fail if we don't have OpenGL or if the data
     // types we assumed in our public interface aren't compatible with the
@@ -1132,15 +1132,19 @@ namespace octave
   {
 #if defined (HAVE_OPENGL)
 
+    // store axes transformation data
+
+    xform = props.get_transform (parent_pix_size);
+
     // setup OpenGL transformation
 
-    Matrix x_zlim = props.get_transform_zlim ();
+    Matrix x_zlim = xform.get_transform_zlim ();
 
     xZ1 = x_zlim(0)-(x_zlim(1)-x_zlim(0))/2;
     xZ2 = x_zlim(1)+(x_zlim(1)-x_zlim(0))/2;
 
-    Matrix x_mat1 = props.get_opengl_matrix_1 ();
-    Matrix x_mat2 = props.get_opengl_matrix_2 ();
+    Matrix x_mat1 = xform.get_opengl_matrix_1 ();
+    Matrix x_mat2 = xform.get_opengl_matrix_2 ();
 
 #if defined (HAVE_FRAMEWORK_OPENGL)
     GLint vw[4];
@@ -1162,10 +1166,6 @@ namespace octave
 
     glClear (GL_DEPTH_BUFFER_BIT);
 
-    // store axes transformation data
-
-    xform = props.get_transform ();
-
 #else
 
     octave_unused_parameter (props);
@@ -3522,8 +3522,8 @@ namespace octave
       return;
 
     Matrix pos = xform.scale (props.get_data_position ());
-    ColumnVector pixpos = get_transform ().transform (pos(0), pos(1),
-                                                      pos(2), false);
+    ColumnVector pixpos = xform.transform (pos(0), pos(1),
+                                           pos(2), false);
     const Matrix bbox = props.get_extent_matrix ();
 
 #  if defined (HAVE_FRAMEWORK_OPENGL)
diff --git a/libinterp/corefcn/gl-render.h b/libinterp/corefcn/gl-render.h
--- a/libinterp/corefcn/gl-render.h
+++ b/libinterp/corefcn/gl-render.h
@@ -62,7 +62,6 @@ namespace octave
     }
 
     virtual void set_viewport (int w, int h);
-    virtual graphics_xform get_transform (void) const { return xform; }
     virtual uint8NDArray get_pixels (int width, int height);
 
     virtual void finish (void);
@@ -189,13 +188,15 @@ namespace octave
     void draw_all_lights (const base_properties& props,
                           std::list<graphics_object>& obj_list);
 
+  protected:
+    // axes transformation data
+    graphics_xform xform;
+    Matrix parent_pix_size;
+
   private:
     // The graphics toolkit associated with the figure being rendered.
     graphics_toolkit toolkit;
 
-    // axes transformation data
-    graphics_xform xform;
-
     // axis limits in model scaled coordinate
     double xmin, xmax;
     double ymin, ymax;
diff --git a/libinterp/corefcn/gl2ps-print.cc b/libinterp/corefcn/gl2ps-print.cc
--- a/libinterp/corefcn/gl2ps-print.cc
+++ b/libinterp/corefcn/gl2ps-print.cc
@@ -305,6 +305,18 @@ namespace octave
 
         in_draw = true;
 
+        octave_value ov = go.get ("__guidata__");
+        if (! ov.isstruct ())
+          error (R"(gl2ps_print: missing __guidata__ structure)");
+        octave_scalar_map m = ov.scalar_map_value ();
+        octave_value pval = m.getfield ("position");
+        if (! pval.is_matrix_type ())
+          error (R"(gl2ps_print: missing __guidata__.position matrix)");
+        Matrix pos = pval.matrix_value ();
+        parent_pix_size = Matrix (2, 1, 0.0);
+        parent_pix_size(0) = pos(2);
+        parent_pix_size(1) = pos(3);
+
         GLint gl2ps_term = GL2PS_PS;
         if (term.find ("eps") != std::string::npos)
           gl2ps_term = GL2PS_EPS;
@@ -504,7 +516,7 @@ namespace octave
     for (auto& txtobj : lst)
       {
         // Get pixel coordinates
-        ColumnVector coord_pix = get_transform ().transform (x, y, z, false);
+        ColumnVector coord_pix = xform.transform (x, y, z, false);
 
         // Translate and rotate
         double rot = rotation * 4.0 * atan (1.0) / 180;
@@ -744,7 +756,7 @@ namespace octave
       return "";
 
     //Use pixel coordinates to conform to gl2ps
-    ColumnVector coord_pix = get_transform ().transform (x, y, z, false);
+    ColumnVector coord_pix = xform.transform (x, y, z, false);
 
     std::ostringstream os;
     os << "<text xml:space=\"preserve\" ";
@@ -1026,7 +1038,7 @@ namespace octave
     // FIXME: handle margin and surrounding box
     // Matrix bbox;
 
-    const Matrix pos = get_transform ().scale (props.get_data_position ());
+    const Matrix pos = xform.scale (props.get_data_position ());
     std::string str = props.get_string ().string_vector_value ().join ("\n");
 
     render_text (str, pos(0), pos(1), pos.numel () > 2 ? pos(2) : 0.0,
@@ -1085,8 +1097,16 @@ namespace octave
 
     gl2ps_renderer rend (fp, term);
 
-    Matrix pos = fig.get ("position").matrix_value ();
+    octave_value ov = fig.get ("__guidata__");
+    if (! ov.isstruct ())
+      error (R"(gl2ps_print: missing __guidata__ structure)");
+    octave_scalar_map m = ov.scalar_map_value ();
+    octave_value pval = m.getfield ("position");
+    if (! pval.is_matrix_type ())
+      error (R"(gl2ps_print: missing __guidata__.position matrix)");
+    Matrix pos = pval.matrix_value ();
     rend.set_viewport (pos(2), pos(3));
+
     rend.draw (fig, stream);
 
     // Make sure buffered commands are finished!!!
diff --git a/libinterp/corefcn/graphics.cc b/libinterp/corefcn/graphics.cc
--- a/libinterp/corefcn/graphics.cc
+++ b/libinterp/corefcn/graphics.cc
@@ -4783,8 +4783,6 @@ axes::properties::init (void)
   // No constraints for hidden transform properties
   update_font ();
 
-  x_zlim.resize (1, 2);
-
   sx = "linear";
   sy = "linear";
   sz = "linear";
@@ -5629,12 +5627,8 @@ axes::properties::update_camera (void)
   Matrix x_view = xform_matrix ();
   Matrix x_projection = xform_matrix ();
   Matrix x_viewport = xform_matrix ();
-  Matrix x_normrender = xform_matrix ();
   Matrix x_pre = xform_matrix ();
 
-  x_render = xform_matrix ();
-  x_render_inv = xform_matrix ();
-
   scale (x_pre, pb(0), pb(1), pb(2));
   translate (x_pre, -0.5, -0.5, -0.5);
   scale (x_pre, xd/(xlimits(1)-xlimits(0)), yd/(ylimits(1)-ylimits(0)),
@@ -5728,12 +5722,129 @@ axes::properties::update_camera (void)
       translate (x_viewport, bb(0)+bb(2)/2, bb(1)+bb(3)/2, 0);
       scale (x_viewport, pix, -pix, 1);
     }
+}
+
+graphics_xform
+axes::properties::get_transform (const Matrix& parent_pix_size) const
+{
+  double xd = (xdir_is ("normal") ? 1 : -1);
+  double yd = (ydir_is ("normal") ? 1 : -1);
+  double zd = (zdir_is ("normal") ? 1 : -1);
+
+  Matrix xlimits = sx.scale (get_xlim ().matrix_value ());
+  Matrix ylimits = sy.scale (get_ylim ().matrix_value ());
+  Matrix zlimits = sz.scale (get_zlim ().matrix_value ());
+
+  double xo = xlimits(xd > 0 ? 0 : 1);
+  double yo = ylimits(yd > 0 ? 0 : 1);
+  double zo = zlimits(zd > 0 ? 0 : 1);
+
+  Matrix pb = get_plotboxaspectratio ().matrix_value ();
+
+  bool autocam = (camerapositionmode_is ("auto")
+                  && cameratargetmode_is ("auto")
+                  && cameraupvectormode_is ("auto")
+                  && cameraviewanglemode_is ("auto"));
+  bool dowarp = (autocam && dataaspectratiomode_is ("auto")
+                 && plotboxaspectratiomode_is ("auto"));
+
+  ColumnVector c_eye (xform_vector ());
+  ColumnVector c_center (xform_vector ());
+  ColumnVector c_upv (xform_vector ());
+
+  c_center = cam2xform (get_cameratarget ().matrix_value ());
+
+  c_eye = cam2xform (get_cameraposition ().matrix_value ());
+
+  c_upv = cam2xform (get_cameraupvector ().matrix_value ());
+
+  Matrix x_view = xform_matrix ();
+  Matrix x_projection = xform_matrix ();
+  Matrix x_viewport = xform_matrix ();
+  Matrix x_normrender = xform_matrix ();
+  Matrix x_pre = xform_matrix ();
+
+  Matrix x_render = xform_matrix ();
+  Matrix x_render_inv = xform_matrix ();
+
+  scale (x_pre, pb(0), pb(1), pb(2));
+  translate (x_pre, -0.5, -0.5, -0.5);
+  scale (x_pre, xd/(xlimits(1)-xlimits(0)), yd/(ylimits(1)-ylimits(0)),
+         zd/(zlimits(1)-zlimits(0)));
+  translate (x_pre, -xo, -yo, -zo);
+
+  xform (c_eye, x_pre);
+  xform (c_center, x_pre);
+  scale (c_upv, pb(0)/(xlimits(1)-xlimits(0)), pb(1)/(ylimits(1)-ylimits(0)),
+         pb(2)/(zlimits(1)-zlimits(0)));
+  translate (c_center, -c_eye(0), -c_eye(1), -c_eye(2));
+
+  ColumnVector F (c_center), f (F), UP (c_upv);
+  normalize (f);
+  normalize (UP);
+
+  if (std::abs (dot (f, UP)) > 1e-15)
+    {
+      double fa = 1 / sqrt (1 - f(2)*f(2));
+      scale (UP, fa, fa, fa);
+    }
+
+  ColumnVector s = cross (f, UP);
+  ColumnVector u = cross (s, f);
+
+  scale (x_view, 1, 1, -1);
+  Matrix l = xform_matrix ();
+  l(0,0) = s(0); l(0,1) = s(1); l(0,2) = s(2);
+  l(1,0) = u(0); l(1,1) = u(1); l(1,2) = u(2);
+  l(2,0) = -f(0); l(2,1) = -f(1); l(2,2) = -f(2);
+  x_view = x_view * l;
+  translate (x_view, -c_eye(0), -c_eye(1), -c_eye(2));
+  scale (x_view, pb(0), pb(1), pb(2));
+  translate (x_view, -0.5, -0.5, -0.5);
+
+  Matrix x_cube = x_view * unit_cube ();
+  ColumnVector cmin = x_cube.row_min ();
+  ColumnVector cmax = x_cube.row_max ();
+  double xM = cmax(0) - cmin(0);
+  double yM = cmax(1) - cmin(1);
+
+  Matrix bb = get_boundingbox (true, parent_pix_size);
+
+  double v_angle;
+  v_angle = get_cameraviewangle ();
+
+  double pf = 1 / (2 * tan ((v_angle / 2) * M_PI / 180.0) * norm (F));
+  scale (x_projection, pf, pf, 1);
+
+  if (dowarp)
+    {
+      xM *= pf;
+      yM *= pf;
+      translate (x_viewport, bb(0)+bb(2)/2, bb(1)+bb(3)/2, 0);
+      scale (x_viewport, bb(2)/xM, -bb(3)/yM, 1);
+    }
+  else
+    {
+      double pix = 1;
+      if (autocam)
+        {
+          if ((bb(2)/bb(3)) > (xM/yM))
+            pix = bb(3);
+          else
+            pix = bb(2);
+        }
+      else
+        pix = (bb(2) < bb(3) ? bb(2) : bb(3));
+      translate (x_viewport, bb(0)+bb(2)/2, bb(1)+bb(3)/2, 0);
+      scale (x_viewport, pix, -pix, 1);
+    }
 
   x_normrender = x_viewport * x_projection * x_view;
 
   x_cube = x_normrender * unit_cube ();
   cmin = x_cube.row_min ();
   cmax = x_cube.row_max ();
+  Matrix x_zlim;
   x_zlim.resize (1, 2);
   x_zlim(0) = cmin(2);
   x_zlim(1) = cmax(2);
@@ -5743,21 +5854,17 @@ axes::properties::update_camera (void)
          zd/(zlimits(1)-zlimits(0)));
   translate (x_render, -xo, -yo, -zo);
 
-  x_viewtransform = x_view;
-  x_projectiontransform = x_projection;
-  x_viewporttransform = x_viewport;
-  x_normrendertransform = x_normrender;
-  x_rendertransform = x_render;
-
   x_render_inv = x_render.inverse ();
 
   // Note: these matrices are a slight modified version of the regular matrices,
   // more suited for OpenGL rendering (x_gl_mat1 => light => x_gl_mat2)
-  x_gl_mat1 = x_view;
+  Matrix x_gl_mat1 = x_view;
   scale (x_gl_mat1, xd/(xlimits(1)-xlimits(0)), yd/(ylimits(1)-ylimits(0)),
          zd/(zlimits(1)-zlimits(0)));
   translate (x_gl_mat1, -xo, -yo, -zo);
-  x_gl_mat2 = x_viewport * x_projection;
+  Matrix x_gl_mat2 = x_viewport * x_projection;
+
+  return graphics_xform (x_render, x_render_inv, sx, sy, sz, x_zlim, x_gl_mat1, x_gl_mat2); 
 }
 
 static bool updating_axes_layout = false;
@@ -6370,7 +6477,7 @@ axes::properties::update_title_position 
       ColumnVector p =
         graphics_xform::xform_vector (bbox(0) + bbox(2)/2,
                                       bbox(1) - 10,
-                                      (x_zlim(0) + x_zlim(1))/2);
+                                      0);
 
       if (x2Dtop)
         {
@@ -6381,7 +6488,7 @@ axes::properties::update_title_position 
           p(1) -= ext(1);
         }
 
-      p = xform.untransform (p(0), p(1), p(2), true);
+      p = xform.pixel2coord (p(0), p(1));
 
       p = convert_label_position (p, title_props, xform, bbox);
 
diff --git a/libinterp/corefcn/graphics.in.h b/libinterp/corefcn/graphics.in.h
--- a/libinterp/corefcn/graphics.in.h
+++ b/libinterp/corefcn/graphics.in.h
@@ -3306,19 +3306,23 @@ class OCTINTERP_API graphics_xform
 public:
   graphics_xform (void)
     : xform (xform_eye ()), xform_inv (xform_eye ()),
-      sx ("linear"), sy ("linear"), sz ("linear"),  zlim (1, 2, 0.0)
+      sx ("linear"), sy ("linear"), sz ("linear"),  zlim (1, 2, 0.0),
+      x_gl_mat1 (xform_eye ()), x_gl_mat2 (xform_eye ())
   {
     zlim(1) = 1.0;
   }
 
   graphics_xform (const Matrix& xm, const Matrix& xim,
                   const scaler& x, const scaler& y, const scaler& z,
-                  const Matrix& zl)
-    : xform (xm), xform_inv (xim), sx (x), sy (y), sz (z), zlim (zl) { }
+                  const Matrix& zl, const Matrix& xglm1,
+                  const Matrix& xglm2)
+    : xform (xm), xform_inv (xim), sx (x), sy (y), sz (z), zlim (zl),
+      x_gl_mat1 (xglm1), x_gl_mat2 (xglm2) { }
 
   graphics_xform (const graphics_xform& g)
     : xform (g.xform), xform_inv (g.xform_inv), sx (g.sx),
-      sy (g.sy), sz (g.sz), zlim (g.zlim) { }
+      sy (g.sy), sz (g.sz), zlim (g.zlim),
+      x_gl_mat1 (g.x_gl_mat1), x_gl_mat2 (g.x_gl_mat2) { }
 
   ~graphics_xform (void) = default;
 
@@ -3330,6 +3334,8 @@ public:
     sy = g.sy;
     sz = g.sz;
     zlim = g.zlim;
+    x_gl_mat1 = g.x_gl_mat1;
+    x_gl_mat2 = g.x_gl_mat2;
 
     return *this;
   }
@@ -3374,11 +3380,25 @@ public:
     return retval;
   }
 
+  Matrix get_transform_matrix (void) const { return xform; }
+  Matrix get_inverse_transform_matrix (void) const { return xform_inv; }
+  Matrix get_opengl_matrix_1 (void) const { return x_gl_mat1; }
+  Matrix get_opengl_matrix_2 (void) const { return x_gl_mat2; }
+  Matrix get_transform_zlim (void) const { return zlim; }
+
+  ColumnVector pixel2coord (double px, double py) const
+  { return untransform (px, py, (zlim(0)+zlim(1))/2); }
+
+  ColumnVector coord2pixel (double x, double y, double z) const
+  { return transform (x, y, z); }
+
 private:
   Matrix xform;
   Matrix xform_inv;
   scaler sx, sy, sz;
   Matrix zlim;
+  Matrix x_gl_mat1;
+  Matrix x_gl_mat2;
 };
 
 enum
@@ -3437,14 +3457,7 @@ public:
     void update_zlabel_position (void);
     void update_title_position (void);
 
-    graphics_xform get_transform (void) const
-    { return graphics_xform (x_render, x_render_inv, sx, sy, sz, x_zlim); }
-
-    Matrix get_transform_matrix (void) const { return x_render; }
-    Matrix get_inverse_transform_matrix (void) const { return x_render_inv; }
-    Matrix get_opengl_matrix_1 (void) const { return x_gl_mat1; }
-    Matrix get_opengl_matrix_2 (void) const { return x_gl_mat2; }
-    Matrix get_transform_zlim (void) const { return x_zlim; }
+    graphics_xform get_transform (const Matrix& parent_pix_size = Matrix ()) const;
 
     int get_xstate (void) const { return xstate; }
     int get_ystate (void) const { return ystate; }
@@ -3487,12 +3500,6 @@ public:
     bool get_zSign (void) const { return zSign; }
     bool get_nearhoriz (void) const { return nearhoriz; }
 
-    ColumnVector pixel2coord (double px, double py) const
-    { return get_transform ().untransform (px, py, (x_zlim(0)+x_zlim(1))/2); }
-
-    ColumnVector coord2pixel (double x, double y, double z) const
-    { return get_transform ().transform (x, y, z); }
-
     void zoom_about_point (const std::string& mode, double x, double y,
                            double factor, bool push_to_zoom_stack = true);
     void zoom (const std::string& mode, double factor,
@@ -3528,12 +3535,6 @@ public:
     scaler sy = scaler ();
     scaler sz = scaler ();
 
-    Matrix x_render = Matrix ();
-    Matrix x_render_inv = Matrix ();
-    Matrix x_gl_mat1 = Matrix ();
-    Matrix x_gl_mat2 = Matrix ();
-    Matrix x_zlim = Matrix ();
-
     std::list<octave_value> zoom_stack = std::list<octave_value> ();
 
     // Axes layout data
@@ -3718,12 +3719,6 @@ public:
       radio_property __autopos_tag__ h , "{none}|subplot"
       // hidden properties for inset
       array_property looseinset hu , Matrix (1, 4, 0.0)
-      // hidden properties for transformation computation
-      array_property x_viewtransform h , Matrix (4, 4, 0.0)
-      array_property x_projectiontransform h , Matrix (4, 4, 0.0)
-      array_property x_viewporttransform h , Matrix (4, 4, 0.0)
-      array_property x_normrendertransform h , Matrix (4, 4, 0.0)
-      array_property x_rendertransform h , Matrix (4, 4, 0.0)
       // hidden properties for minor ticks
       row_vector_property xminortickvalues h , Matrix ()
       row_vector_property yminortickvalues h , Matrix ()
diff --git a/libinterp/dldfcn/__init_fltk__.cc b/libinterp/dldfcn/__init_fltk__.cc
--- a/libinterp/dldfcn/__init_fltk__.cc
+++ b/libinterp/dldfcn/__init_fltk__.cc
@@ -1197,7 +1197,7 @@ private:
       {
         axes::properties& ap =
           dynamic_cast<axes::properties&> (ax.get_properties ());
-        ColumnVector pp = ap.pixel2coord (px, py);
+        ColumnVector pp = ap.get_transform ().pixel2coord (px, py);
         xx = pp(0);
         yy = pp(1);
       }
@@ -1293,17 +1293,18 @@ private:
         axes::properties& ap =
           dynamic_cast<axes::properties&> (ax.get_properties ());
 
-        Matrix x_zlim = ap.get_transform_zlim ();
+        graphics_xform xform = ap.get_transform ();
+        Matrix x_zlim = xform.get_transform_zlim ();
         Matrix pos (2, 3, 0.0);
 
         // front point (nearest to the viewer)
-        ColumnVector tmp = ap.get_transform ().untransform (px, py, x_zlim(0));
+        ColumnVector tmp = xform.untransform (px, py, x_zlim(0));
         pos(0,0) = tmp(0);
         pos(0,1) = tmp(1);
         pos(0,2) = tmp(2);
 
         // back point (furthest from the viewer)
-        tmp = ap.get_transform ().untransform (px, py, x_zlim(1));
+        tmp = xform.untransform (px, py, x_zlim(1));
         pos(1,0) = tmp(0);
         pos(1,1) = tmp(1);
         pos(1,2) = tmp(2);
diff --git a/scripts/plot/util/print.m b/scripts/plot/util/print.m
--- a/scripts/plot/util/print.m
+++ b/scripts/plot/util/print.m
@@ -417,22 +417,18 @@ function print (varargin)
       endfor
     endif
 
-    ## print() requires figure units to be "pixels"
-    props(end+1).h = opts.figure;
-    props(end).name = "units";
-    props(end).value = {get(opts.figure, "units")};
-    set (opts.figure, "units", "pixels");
-    nfig += 1;
-
     ## graphics toolkit translates figure position to eps bbox (points)
+    ## 1/28/18  This should no longer be necessary, but the FLTK toolkit
+    ## on-screen axes position shifts if this is not restored.
     fpos = get (opts.figure, "position");
     props(end+1).h = opts.figure;
     props(end).name = "position";
     props(end).value = {fpos};
-    fpos(3:4) = opts.canvas_size;
-    set (opts.figure, "position", fpos);
     nfig += 1;
 
+    ## print() requires figure units to be "pixels"
+    __guidata__.position = [0 0 opts.canvas_size];
+
     ## Implement InvertHardCopy option
     do_hardcopy = strcmp (get (opts.figure, "inverthardcopy"), "on");
 
@@ -544,6 +540,8 @@ function print (varargin)
       endif
     endif
 
+    set (opts.figure, "__guidata__", __guidata__);
+
     ## call the graphics toolkit print script
     switch (tk)
       case "gnuplot"
diff --git a/scripts/plot/util/private/__opengl_print__.m b/scripts/plot/util/private/__opengl_print__.m
--- a/scripts/plot/util/private/__opengl_print__.m
+++ b/scripts/plot/util/private/__opengl_print__.m
@@ -25,6 +25,9 @@ function opts = __opengl_print__ (opts)
 
   dos_shell = (ispc () && ! isunix ());
 
+  set (0, "currentfigure", opts.figure);
+  drawnow ("expose");
+
   if (! isempty (opts.fig2dev_binary))
     ## fig2dev is prefered for conversion to emf
     fig2dev_devices = {"pstex", "mf", "emf"};
@@ -150,7 +153,8 @@ function opts = __opengl_print__ (opts)
       fprintf ("opengl-pipeline: '%s'\n", pipeline{n});
     endif
 
-    if (strcmp (get (opts.figure, "visible"), "on"))
+    if (strcmp (get (opts.figure, "visible"), "on") ||
+        strcmp (get (opts.figure, "__graphics_toolkit__"), "qt"))
       ## Use toolkits "print_figure" method
       drawnow (gl2ps_device{n}, ['|' pipeline{n}]);
     else
