GNU Octave  8.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
gl2ps-print.cc
Go to the documentation of this file.
1 ////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (C) 2009-2023 The Octave Project Developers
4 //
5 // See the file COPYRIGHT.md in the top-level directory of this
6 // distribution or <https://octave.org/copyright/>.
7 //
8 // This file is part of Octave.
9 //
10 // Octave is free software: you can redistribute it and/or modify it
11 // under the terms of the GNU General Public License as published by
12 // the Free Software Foundation, either version 3 of the License, or
13 // (at your option) any later version.
14 //
15 // Octave is distributed in the hope that it will be useful, but
16 // WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU General Public License for more details.
19 //
20 // You should have received a copy of the GNU General Public License
21 // along with Octave; see the file COPYING. If not, see
22 // <https://www.gnu.org/licenses/>.
23 //
24 ////////////////////////////////////////////////////////////////////////
25 
26 #if defined (HAVE_CONFIG_H)
27 # include "config.h"
28 #endif
29 
30 // Both header files are required outside of HAVE_GLP2S_H
31 #include "errwarn.h"
32 #include "gl2ps-print.h"
33 
34 #if defined (HAVE_GL2PS_H) && defined (HAVE_OPENGL)
35 
36 #include <cstdio>
37 
38 #include <limits>
39 
40 #include <gl2ps.h>
41 
42 #include "file-ops.h"
43 #include "lo-mappers.h"
44 #include "oct-locbuf.h"
45 #include "oct-env.h"
46 #include "unistd-wrappers.h"
47 #include "unistr-wrappers.h"
48 #include "unwind-prot.h"
49 
50 #include "gl-render.h"
51 #include "interpreter-private.h"
52 #include "oct-opengl.h"
53 #include "sighandlers.h"
54 #include "sysdep.h"
55 #include "text-renderer.h"
56 
58 
59 class
60 OCTINTERP_API
61 gl2ps_renderer : public opengl_renderer
62 {
63 public:
64 
65  gl2ps_renderer (opengl_functions& glfcns, FILE *_fp,
66  const std::string& _term)
67  : opengl_renderer (glfcns), m_fp (_fp), m_term (_term), m_fontsize (),
68  m_fontname (), m_buffer_overflow (false), m_svg_def_index (0)
69  { }
70 
71  ~gl2ps_renderer (void) = default;
72 
73  // FIXME: should we import the functions from the base class and
74  // overload them here, or should we use a different name so we don't
75  // have to do this? Without the using declaration or a name change,
76  // the base class functions will be hidden. That may be OK, but it
77  // can also cause some confusion.
79 
80  void draw (const graphics_object& go, const std::string& print_cmd);
81 
82 protected:
83 
84  Matrix render_text (const std::string& txt,
85  double x, double y, double z,
86  int halign, int valign, double rotation = 0.0);
87 
88  void set_font (const base_properties& props);
89 
90  static bool has_alpha (const graphics_handle& h)
91  {
92  bool retval = false;
93 
94  gh_manager& gh_mgr = __get_gh_manager__ ();
95 
96  graphics_object go = gh_mgr.get_object (h);
97 
98  if (! go.valid_object ())
99  return retval;
100 
101  if (go.isa ("axes") || go.isa ("hggroup"))
102  {
103  Matrix children = go.get ("children").matrix_value ();
104  for (octave_idx_type ii = 0; ii < children.numel (); ii++)
105  {
106  retval = has_alpha (graphics_handle (children(ii)));
107  if (retval)
108  break;
109  }
110  }
111  else if (go.isa ("patch") || go.isa ("surface"))
112  {
113  octave_value fa = go.get ("facealpha");
114  if (fa.is_scalar_type () && fa.is_double_type ()
115  && fa.double_value () < 1)
116  retval = true;
117  }
118  else if (go.isa ("scatter"))
119  {
120  octave_value fa = go.get ("markerfacealpha");
121  if (fa.is_scalar_type () && fa.is_double_type ()
122  && fa.double_value () < 1)
123  retval = true;
124  }
125 
126  return retval;
127  }
128 
129  void draw_axes (const axes::properties& props)
130  {
131  // Initialize a sorting tree (viewport) in gl2ps for each axes
132  GLint vp[4];
133  m_glfcns.glGetIntegerv (GL_VIEWPORT, vp);
134  gl2psBeginViewport (vp);
135 
136 
137  // Don't remove hidden primitives when some of them are transparent
138  GLint opts;
139  gl2psGetOptions (&opts);
140  if (has_alpha (props.get___myhandle__ ()))
141  {
142  opts &= ~GL2PS_OCCLUSION_CULL;
143  // FIXME: currently the GL2PS_BLEND (which is more an equivalent of
144  // GL_ALPHA_TEST than GL_BLEND) is not working on a per primitive
145  // basis. We thus set it once per viewport.
146  gl2psEnable (GL2PS_BLEND);
147  }
148  else
149  {
150  opts |= GL2PS_OCCLUSION_CULL;
151  gl2psDisable (GL2PS_BLEND);
152  }
153 
154  gl2psSetOptions (opts);
155 
156  // Draw and finish () or there may be primitives missing in the gl2ps
157  // output.
159  finish ();
160 
161  // Finalize viewport
162  GLint state = gl2psEndViewport ();
163  if (state == GL2PS_NO_FEEDBACK && props.is_visible ())
164  warning ("gl2ps_renderer::draw_axes: empty feedback buffer and/or nothing else to print");
165  else if (state == GL2PS_ERROR)
166  error ("gl2ps_renderer::draw_axes: gl2psEndPage returned GL2PS_ERROR");
167 
168  m_buffer_overflow |= (state == GL2PS_OVERFLOW);
169 
170  // Don't draw background for subsequent viewports (legends, subplots,
171  // etc.)
172  gl2psGetOptions (&opts);
173  opts &= ~GL2PS_DRAW_BACKGROUND;
174  gl2psSetOptions (opts);
175  }
176 
177  void draw_text (const text::properties& props);
178 
179  void draw_image (const image::properties& props);
180  void draw_pixels (int w, int h, const float *data);
181  void draw_pixels (int w, int h, const uint8_t *data);
182  void draw_pixels (int w, int h, const uint16_t *data);
183 
184  void init_marker (const std::string& m, double size, float width)
185  {
186  opengl_renderer::init_marker (m, size, width);
187 
188  // FIXME: gl2ps can't handle closed contours so we set linecap/linejoin
189  // round to obtain a better looking result for some markers.
190  if (m == "o" || m == "v" || m == "^" || m == ">" || m == "<" || m == "h"
191  || m == "hexagram" || m == "p" || m == "pentagram")
192  {
193  set_linejoin ("round");
194  set_linecap ("round");
195  }
196  else
197  {
198  set_linejoin ("miter");
199  set_linecap ("square");
200  }
201  }
202 
203  void set_linestyle (const std::string& s, bool use_stipple = false,
204  double linewidth = 0.5)
205  {
206  opengl_renderer::set_linestyle (s, use_stipple, linewidth);
207 
208  if (s == "-" && ! use_stipple)
209  gl2psDisable (GL2PS_LINE_STIPPLE);
210  else
211  gl2psEnable (GL2PS_LINE_STIPPLE);
212  }
213 
214  void set_linecap (const std::string& s)
215  {
217 
218 #if defined (HAVE_GL2PSLINEJOIN)
219  if (s == "butt")
220  gl2psLineCap (GL2PS_LINE_CAP_BUTT);
221  else if (s == "square")
222  gl2psLineCap (GL2PS_LINE_CAP_SQUARE);
223  else if (s == "round")
224  gl2psLineCap (GL2PS_LINE_CAP_ROUND);
225 #endif
226  }
227 
228  void set_linejoin (const std::string& s)
229  {
231 
232 #if defined (HAVE_GL2PSLINEJOIN)
233  if (s == "round")
234  gl2psLineJoin (GL2PS_LINE_JOIN_ROUND);
235  else if (s == "miter")
236  gl2psLineJoin (GL2PS_LINE_JOIN_MITER);
237  else if (s == "chamfer")
238  gl2psLineJoin (GL2PS_LINE_JOIN_BEVEL);
239 #endif
240  }
241 
242  void set_polygon_offset (bool on, float offset = 0.0f)
243  {
244  if (on)
245  {
247  gl2psEnable (GL2PS_POLYGON_OFFSET_FILL);
248  }
249  else
250  {
251  gl2psDisable (GL2PS_POLYGON_OFFSET_FILL);
253  }
254  }
255 
256  void set_linewidth (float w)
257  {
258  gl2psLineWidth (w);
259  }
260 
261 private:
262 
263  // Use xform to compute the coordinates of the string list
264  // that have been parsed by freetype.
265  void fix_strlist_position (double x, double y, double z,
266  Matrix box, double rotation,
267  std::list<text_renderer::string>& lst);
268 
269  // Build an svg text element from a list of parsed strings
270  std::string format_svg_element (std::string str, Matrix bbox,
271  double rotation, ColumnVector coord_pix,
272  Matrix color);
273 
274  std::string strlist_to_svg (double x, double y, double z, Matrix box,
275  double rotation,
276  std::list<text_renderer::string>& lst);
277 
278  // Build a list of postscript commands from a list of parsed strings.
279  std::string strlist_to_ps (double x, double y, double z, Matrix box,
280  double rotation,
281  std::list<text_renderer::string>& lst);
282 
283  int alignment_to_mode (int ha, int va) const;
284 
285  FILE *m_fp;
286  caseless_str m_term;
287  double m_fontsize;
288  std::string m_fontname;
289  bool m_buffer_overflow;
290  std::size_t m_svg_def_index;
291 };
292 
293 static bool
294 has_2D_axes (const graphics_handle& h)
295 {
296  bool retval = true;
297 
298  gh_manager& gh_mgr = __get_gh_manager__ ();
299 
300  graphics_object go = gh_mgr.get_object (h);
301 
302  if (! go.valid_object ())
303  return retval;
304 
305  if (go.isa ("figure") || go.isa ("uipanel"))
306  {
307  Matrix children = go.get ("children").matrix_value ();
308  for (octave_idx_type ii = 0; ii < children.numel (); ii++)
309  {
310  retval = has_2D_axes (graphics_handle (children(ii)));
311  if (! retval)
312  break;
313  }
314  }
315  else if (go.isa ("axes"))
316  {
317  axes::properties& ap
318  = reinterpret_cast<axes::properties&> (go.get_properties ());
319  retval = ap.get_is2D (true);
320  }
321 
322  return retval;
323 }
324 
325 static std::string
326 get_title (const graphics_handle& h)
327 {
328  std::string retval;
329 
330  gh_manager& gh_mgr = __get_gh_manager__ ();
331 
332  graphics_object go = gh_mgr.get_object (h);
333 
334  if (! go.valid_object ())
335  return retval;
336 
337  if (go.isa ("figure"))
338  {
340  = reinterpret_cast<figure::properties&> (go.get_properties ());
341 
342  retval = fp.get_title ();
343  }
344 
345  return retval;
346 }
347 
348 void
349 gl2ps_renderer::draw (const graphics_object& go, const std::string& print_cmd)
350 {
351  static bool in_draw = false;
352  static std::string old_print_cmd;
353  static GLint buffsize;
354 
355  if (! in_draw)
356  {
357  unwind_protect frame;
358 
359  frame.protect_var (in_draw);
360 
361  in_draw = true;
362 
363  GLint gl2ps_term = GL2PS_PS;
364  if (m_term.find ("eps") != std::string::npos)
365  gl2ps_term = GL2PS_EPS;
366  else if (m_term.find ("pdf") != std::string::npos)
367  gl2ps_term = GL2PS_PDF;
368  else if (m_term.find ("ps") != std::string::npos)
369  gl2ps_term = GL2PS_PS;
370  else if (m_term.find ("svg") != std::string::npos)
371  gl2ps_term = GL2PS_SVG;
372  else if (m_term.find ("pgf") != std::string::npos)
373  gl2ps_term = GL2PS_PGF;
374  else if (m_term.find ("tex") != std::string::npos)
375  gl2ps_term = GL2PS_TEX;
376  else
377  warning ("gl2ps_renderer::draw: Unknown terminal %s, using 'ps'",
378  m_term.c_str ());
379 
380  GLint gl2ps_text = 0;
381  if (m_term.find ("notxt") != std::string::npos)
382  gl2ps_text = GL2PS_NO_TEXT;
383 
384  // Find Title for plot
385  const graphics_handle& myhandle = go.get ("__myhandle__");
386  std::string plot_title = get_title (myhandle);
387  if (plot_title.empty ())
388  plot_title = "Octave plot";
389 
390  // Default sort order optimizes for 3D plots
391  GLint gl2ps_sort = GL2PS_BSP_SORT;
392 
393  // FIXME: gl2ps does not provide a way to change the sorting algorithm
394  // on a viewport basis, we thus disable sorting only if all axes are 2D
395  if (has_2D_axes (myhandle))
396  gl2ps_sort = GL2PS_NO_SORT;
397 
398  // Use a temporary file in case an overflow happens
399  std::string tmpfile (sys::tempnam (sys::env::get_temp_directory (),
400  "oct-"));
401  FILE *tmpf = sys::fopen_tmp (tmpfile, "w+b");
402 
403  if (! tmpf)
404  error ("gl2ps_renderer::draw: couldn't open temporary file for printing");
405 
406  frame.add ([=] () { std::fclose (tmpf); });
407 
408  // Reset buffsize, unless this is 2nd pass of a texstandalone print.
409  if (m_term.find ("tex") == std::string::npos)
410  buffsize = 2*1024*1024;
411  else
412  buffsize /= 2;
413 
414  m_buffer_overflow = true;
415 
416  while (m_buffer_overflow)
417  {
418  m_buffer_overflow = false;
419  buffsize *= 2;
420 
421  std::fseek (tmpf, 0, SEEK_SET);
422  octave_ftruncate_wrapper (fileno (tmpf), 0);
423 
424  // For LaTeX output the print process uses 2 drawnow() commands.
425  // The first one is for the pdf/ps/eps graph to be included. The
426  // print_cmd is saved as old_print_cmd. Then the second drawnow()
427  // outputs the tex-file and the graphic filename to be included is
428  // extracted from old_print_cmd.
429 
430  std::string include_graph;
431 
432  std::size_t found_redirect = old_print_cmd.find ('>');
433 
434  if (found_redirect != std::string::npos)
435  include_graph = old_print_cmd.substr (found_redirect + 1);
436  else
437  include_graph = old_print_cmd;
438 
439  std::size_t n_begin = include_graph.find_first_not_of (R"( "')");
440 
441  if (n_begin != std::string::npos)
442  {
443  // Strip any quote characters characters around filename
444  std::size_t n_end = include_graph.find_last_not_of (R"( "')");
445  include_graph = include_graph.substr (n_begin,
446  n_end - n_begin + 1);
447  // Strip path from filename
448  n_begin = include_graph.find_last_of (sys::file_ops::dir_sep_chars ());
449  include_graph = include_graph.substr (n_begin + 1);
450  }
451  else
452  include_graph = "foobar-inc";
453 
454  // FIXME: workaround gl2ps drawing 2 background planes, the first
455  // eventually being black and producing visual artifacts
456  const figure::properties& fprop
457  = dynamic_cast<const figure::properties&> (go.get_properties ());
458  Matrix c = fprop.get_color_rgb ();
459  m_glfcns.glClearColor (c(0), c(1), c(2), 1);
460 
461  // Allow figures to be printed at arbitrary resolution
462  set_device_pixel_ratio (fprop.get___device_pixel_ratio__ ());
463 
464  // GL2PS_SILENT was removed to allow gl2ps to print errors on stderr
465  GLint ret = gl2psBeginPage (plot_title.c_str (), "Octave",
466  nullptr, gl2ps_term, gl2ps_sort,
467  (GL2PS_BEST_ROOT
468  | gl2ps_text
469  | GL2PS_DRAW_BACKGROUND
470  | GL2PS_NO_PS3_SHADING
471  | GL2PS_USE_CURRENT_VIEWPORT),
472  GL_RGBA, 0, nullptr, 0, 0, 0,
473  buffsize, tmpf, include_graph.c_str ());
474  if (ret == GL2PS_ERROR)
475  {
476  old_print_cmd.clear ();
477  error ("gl2ps_renderer::draw: gl2psBeginPage returned GL2PS_ERROR");
478  }
479 
481 
482  if (m_buffer_overflow)
483  warning ("gl2ps_renderer::draw: retrying with buffer size: %.1E B\n", double (2*buffsize));
484 
485  if (! m_buffer_overflow)
486  old_print_cmd = print_cmd;
487 
488  // Don't check return value of gl2psEndPage, it is not meaningful.
489  // Errors and warnings are checked after gl2psEndViewport in
490  // gl2ps_renderer::draw_axes instead.
491  gl2psEndPage ();
492  }
493 
494  // Copy temporary file to pipe
495  std::fseek (tmpf, 0, SEEK_SET);
496  char str[8192]; // 8 kB is a common kernel buffersize
497  std::size_t nread, nwrite;
498  nread = 1;
499 
500  // In EPS terminal read the header line by line and insert a
501  // new procedure
502  const char *fcn = "/SRX { gsave FCT moveto rotate xshow grestore } BD\n";
503  bool header_found = ! (m_term.find ("eps") != std::string::npos
504  || m_term.find ("svg") != std::string::npos);
505 
506  while (! feof (tmpf) && nread)
507  {
508  if (! header_found && std::fgets (str, 8192, tmpf))
509  nread = strlen (str);
510  else
511  nread = std::fread (str, 1, 8192, tmpf);
512 
513  if (nread)
514  {
515  if (! header_found && std::strncmp (str, "/SBCR", 5) == 0)
516  {
517  header_found = true;
518  nwrite = std::fwrite (fcn, 1, strlen (fcn), m_fp);
519  if (nwrite != strlen (fcn))
520  {
521  // FIXME: is this the best thing to do here?
523  error ("gl2ps_renderer::draw: internal pipe error");
524  }
525  }
526  else if (m_term.find ("svg") != std::string::npos)
527  {
528  // FIXME: gl2ps uses pixel units for SVG format.
529  // Modify resulting svg to use points instead.
530  // Remove this "else if" block, and
531  // make header_found true for SVG if gl2ps is fixed.
532 
533  // Specify number of characters because STR may have
534  // come from std::fread and not end with a NUL
535  // character.
536  std::string srchstr (str, nread);
537  std::size_t pos = srchstr.find ("<svg ");
538  if (! header_found && pos != std::string::npos)
539  {
540  header_found = true;
541  pos = srchstr.find ("px");
542  if (pos != std::string::npos)
543  {
544  srchstr[pos+1] = 't'; // "px" -> "pt"
545  // Assume the second occurrence is at the same line
546  pos = srchstr.find ("px", pos);
547  srchstr[pos+1] = 't'; // "px" -> "pt"
548  std::strcpy (str, srchstr.c_str ());
549  }
550  }
551  }
552 
553  nwrite = std::fwrite (str, 1, nread, m_fp);
554  if (nwrite != nread)
555  {
556  // FIXME: is this the best thing to do here?
557  respond_to_pending_signals (); // Clear SIGPIPE signal
558  error ("gl2ps_renderer::draw: internal pipe error");
559  }
560  }
561  }
562  }
563  else
565 }
566 
567 int
568 gl2ps_renderer::alignment_to_mode (int ha, int va) const
569 {
570  int gl2psa = GL2PS_TEXT_BL;
571 
572  if (ha == 0)
573  {
574  if (va == 0 || va == 3)
575  gl2psa=GL2PS_TEXT_BL;
576  else if (va == 2)
577  gl2psa=GL2PS_TEXT_TL;
578  else if (va == 1)
579  gl2psa=GL2PS_TEXT_CL;
580  }
581  else if (ha == 2)
582  {
583  if (va == 0 || va == 3)
584  gl2psa=GL2PS_TEXT_BR;
585  else if (va == 2)
586  gl2psa=GL2PS_TEXT_TR;
587  else if (va == 1)
588  gl2psa=GL2PS_TEXT_CR;
589  }
590  else if (ha == 1)
591  {
592  if (va == 0 || va == 3)
593  gl2psa=GL2PS_TEXT_B;
594  else if (va == 2)
595  gl2psa=GL2PS_TEXT_T;
596  else if (va == 1)
597  gl2psa=GL2PS_TEXT_C;
598  }
599 
600  return gl2psa;
601 }
602 
603 void
604 gl2ps_renderer::fix_strlist_position (double x, double y, double z,
605  Matrix box, double rotation,
606  std::list<text_renderer::string>& lst)
607 {
608  for (auto& txtobj : lst)
609  {
610  // Get pixel coordinates
611  ColumnVector coord_pix = get_transform ().transform (x, y, z, false);
612 
613  // Translate and rotate
614  double rot = rotation * 4.0 * atan (1.0) / 180;
615  coord_pix(0) += (txtobj.get_x () + box(0))* cos (rot)
616  - (txtobj.get_y () + box(1))* sin (rot);
617  coord_pix(1) -= (txtobj.get_y () + box(1))* cos (rot)
618  + (txtobj.get_x () + box(0))* sin (rot);
619 
620  GLint vp[4];
621  m_glfcns.glGetIntegerv (GL_VIEWPORT, vp);
622 
623  txtobj.set_x (coord_pix(0));
624  txtobj.set_y (vp[3] - coord_pix(1));
625  txtobj.set_z (coord_pix(2));
626  }
627 }
628 
629 static std::string
630 code_to_symbol (uint32_t code)
631 {
632  std::string retval;
633 
634  uint32_t idx = code - 945;
635  if (idx < 25)
636  {
637  std::string characters ("abgdezhqiklmnxoprVstufcyw");
638  retval = characters[idx];
639  return retval;
640  }
641 
642  idx = code - 913;
643  if (idx < 25)
644  {
645  std::string characters ("ABGDEZHQIKLMNXOPRVSTUFCYW");
646  retval = characters[idx];
647  }
648  else if (code == 978)
649  retval = "U";
650  else if (code == 215)
651  retval = "\xb4";
652  else if (code == 177)
653  retval = "\xb1";
654  else if (code == 8501)
655  retval = "\xc0";
656  else if (code == 8465)
657  retval = "\xc1";
658  else if (code == 8242)
659  retval = "\xa2";
660  else if (code == 8736)
661  retval = "\xd0";
662  else if (code == 172)
663  retval = "\xd8";
664  else if (code == 9829)
665  retval = "\xa9";
666  else if (code == 8472)
667  retval = "\xc3";
668  else if (code == 8706)
669  retval = "\xb6";
670  else if (code == 8704)
671  retval = "\x22";
672  else if (code == 9827)
673  retval = "\xa7";
674  else if (code == 9824)
675  retval = "\xaa";
676  else if (code == 8476)
677  retval = "\xc2";
678  else if (code == 8734)
679  retval = "\xa5";
680  else if (code == 8730)
681  retval = "\xd6";
682  else if (code == 8707)
683  retval = "\x24";
684  else if (code == 9830)
685  retval = "\xa8";
686  else if (code == 8747)
687  retval = "\xf2";
688  else if (code == 8727)
689  retval = "\x2a";
690  else if (code == 8744)
691  retval = "\xda";
692  else if (code == 8855)
693  retval = "\xc4";
694  else if (code == 8901)
695  retval = "\xd7";
696  else if (code == 8728)
697  retval = "\xb0";
698  else if (code == 8745)
699  retval = "\xc7";
700  else if (code == 8743)
701  retval = "\xd9";
702  else if (code == 8856)
703  retval = "\xc6";
704  else if (code == 8729)
705  retval = "\xb7";
706  else if (code == 8746)
707  retval = "\xc8";
708  else if (code == 8853)
709  retval = "\xc5";
710  else if (code == 8804)
711  retval = "\xa3";
712  else if (code == 8712)
713  retval = "\xce";
714  else if (code == 8839)
715  retval = "\xca";
716  else if (code == 8801)
717  retval = "\xba";
718  else if (code == 8773)
719  retval = "\x40";
720  else if (code == 8834)
721  retval = "\xcc";
722  else if (code == 8805)
723  retval = "\xb3";
724  else if (code == 8715)
725  retval = "\x27";
726  else if (code == 8764)
727  retval = "\x7e";
728  else if (code == 8733)
729  retval = "\xb5";
730  else if (code == 8838)
731  retval = "\xcd";
732  else if (code == 8835)
733  retval = "\xc9";
734  else if (code == 8739)
735  retval = "\xbd";
736  else if (code == 8776)
737  retval = "\xbb";
738  else if (code == 8869)
739  retval = "\x5e";
740  else if (code == 8656)
741  retval = "\xdc";
742  else if (code == 8592)
743  retval = "\xac";
744  else if (code == 8658)
745  retval = "\xde";
746  else if (code == 8594)
747  retval = "\xae";
748  else if (code == 8596)
749  retval = "\xab";
750  else if (code == 8593)
751  retval = "\xad";
752  else if (code == 8595)
753  retval = "\xaf";
754  else if (code == 8970)
755  retval = "\xeb";
756  else if (code == 8971)
757  retval = "\xfb";
758  else if (code == 10216)
759  retval = "\xe1";
760  else if (code == 10217)
761  retval = "\xf1";
762  else if (code == 8968)
763  retval = "\xe9";
764  else if (code == 8969)
765  retval = "\xf9";
766  else if (code == 8800)
767  retval = "\xb9";
768  else if (code == 8230)
769  retval = "\xbc";
770  else if (code == 176)
771  retval = "\xb0";
772  else if (code == 8709)
773  retval = "\xc6";
774  else if (code == 169)
775  retval = "\xd3";
776 
777  if (retval.empty ())
778  warning ("print: unhandled symbol %d", code);
779 
780  return retval;
781 }
782 
783 static std::string
784 select_font (caseless_str fn, bool isbold, bool isitalic)
785 {
786  std::transform (fn.begin (), fn.end (), fn.begin (), ::tolower);
787  std::string fontname;
788  if (fn == "times" || fn == "times-roman")
789  {
790  if (isitalic && isbold)
791  fontname = "Times-BoldItalic";
792  else if (isitalic)
793  fontname = "Times-Italic";
794  else if (isbold)
795  fontname = "Times-Bold";
796  else
797  fontname = "Times-Roman";
798  }
799  else if (fn == "courier")
800  {
801  if (isitalic && isbold)
802  fontname = "Courier-BoldOblique";
803  else if (isitalic)
804  fontname = "Courier-Oblique";
805  else if (isbold)
806  fontname = "Courier-Bold";
807  else
808  fontname = "Courier";
809  }
810  else if (fn == "symbol")
811  fontname = "Symbol";
812  else if (fn == "zapfdingbats")
813  fontname = "ZapfDingbats";
814  else
815  {
816  if (isitalic && isbold)
817  fontname = "Helvetica-BoldOblique";
818  else if (isitalic)
819  fontname = "Helvetica-Oblique";
820  else if (isbold)
821  fontname = "Helvetica-Bold";
822  else
823  fontname = "Helvetica";
824  }
825  return fontname;
826 }
827 
828 static void
829 escape_character (const std::string chr, std::string& str)
830 {
831  std::size_t idx = str.find (chr);
832  while (idx != std::string::npos)
833  {
834  str.insert (idx, 1, '\\');
835  idx = str.find (chr, idx + 2);
836  }
837 }
838 
839 std::string
840 gl2ps_renderer::format_svg_element (std::string str, Matrix box,
841  double rotation, ColumnVector coord_pix,
842  Matrix color)
843 {
844  // Extract <defs> elements and change their id to avoid conflict with
845  // defs coming from another svg string
846  std::string::size_type n1 = str.find ("<defs>");
847  if (n1 == std::string::npos)
848  return std::string ();
849 
850  std::string id, new_id;
851  n1 = str.find ("<path", ++n1);
852  std::string::size_type n2;
853 
854  while (n1 != std::string::npos)
855  {
856  // Extract the identifier id='identifier'
857  n1 = str.find ("id='", n1) + 4;
858  n2 = str.find ("'", n1);
859  id = str.substr (n1, n2-n1);
860 
861  new_id = std::to_string (m_svg_def_index) + "-" + id ;
862 
863  str.replace (n1, n2-n1, new_id);
864 
865  std::string::size_type n_ref = str.find ("#" + id);
866 
867  while (n_ref != std::string::npos)
868  {
869  str.replace (n_ref + 1, id.length (), new_id);
870  n_ref = str.find ("#" + id);
871  }
872 
873  n1 = str.find ("<path", n1);
874  }
875 
876  m_svg_def_index++;
877 
878  n1 = str.find ("<defs>");
879  n2 = str.find ("</defs>") + 7;
880 
881  std::string defs = str.substr (n1, n2-n1);
882 
883  // Extract the group containing the <use> elements and transform its
884  // coordinates using the bbox and coordinates info.
885 
886  // Extract the original viewBox anchor
887  n1 = str.find ("viewBox='") + 9;
888  if (n1 == std::string::npos)
889  return std::string ();
890 
891  n2 = str.find (" ", n1);
892  double original_x0 = std::stod (str.substr (n1, n2-n1));
893 
894  n1 = n2+1;
895  n2 = str.find (" ", n1);
896  double original_y0 = std::stod (str.substr (n1, n2-n1));
897 
898  // First look for local transform in the original svg
899  std::string orig_trans;
900  n1 = str.find ("<g id='page1' transform='");
901  if (n1 != std::string::npos)
902  {
903  n1 += 25;
904  n2 = str.find ("'", n1);
905  orig_trans = str.substr (n1, n2-n1);
906  n1 = n2 + 1;
907  }
908  else
909  {
910  n1 = str.find ("<g id='page1'");
911  n1 += 13;
912  }
913 
914  n2 = str.find ("</g>", n1) + 4;
915 
916  // The first applied transformation is the right-most
917  // 1* Apply original transform
918  std::string tform = orig_trans;
919 
920  // 2* Move the anchor to the final position
921  tform = std::string ("translate")
922  + "(" + std::to_string (box(0) - original_x0 + coord_pix(0))
923  + "," + std::to_string (-(box(3) + box(1)) - original_y0 + coord_pix(1))
924  + ") " + tform;
925 
926  // 3* Rotate around the final position
927  if (rotation != 0)
928  tform = std::string ("rotate")
929  + "(" + std::to_string (-rotation)
930  + "," + std::to_string (coord_pix(0))
931  + "," + std::to_string (coord_pix(1))
932  + ") " + tform;
933 
934  // Fill color
935  std::string fill = "fill='rgb("
936  + std::to_string (static_cast<uint8_t> (color(0) * 255.0)) + ","
937  + std::to_string (static_cast<uint8_t> (color(1) * 255.0)) + ","
938  + std::to_string (static_cast<uint8_t> (color(2) * 255.0)) + ")' ";
939 
940  std::string use_group = "<g "
941  + fill
942  + "transform='" + tform + "'"
943  + str.substr (n1, n2-n1);
944 
945  return defs + "\n" + use_group;
946 }
947 
948 std::string
949 gl2ps_renderer::strlist_to_svg (double x, double y, double z,
950  Matrix box, double rotation,
951  std::list<text_renderer::string>& lst)
952 {
953  //Use pixel coordinates to conform to gl2ps
954  ColumnVector coord_pix = get_transform ().transform (x, y, z, false);
955 
956  if (lst.empty ())
957  return "";
958 
959  // This may already be an svg image.
960  std::string svg = lst.front ().get_svg_element ();
961  if (! svg.empty ())
962  return format_svg_element (svg, box, rotation, coord_pix,
963  lst.front ().get_color ());
964 
965  // Rotation and translation are applied to the whole group
966  std::ostringstream os;
967  os << R"(<g xml:space="preserve" )";
968  os << "transform=\""
969  << "translate(" << coord_pix(0) + box(0) << "," << coord_pix(1) - box(1)
970  << ") rotate(" << -rotation << "," << -box(0) << "," << box(1)
971  << ")\" ";
972 
973  // Use the first entry for the base text font
974  auto p = lst.begin ();
975  std::string name = p->get_family ();
976  std::string weight = p->get_weight ();
977  std::string angle = p->get_angle ();
978  double size = p->get_size ();
979 
980  os << "font-family=\"" << name << "\" "
981  << "font-weight=\"" << weight << "\" "
982  << "font-style=\"" << angle << "\" "
983  << "font-size=\"" << size << "\">";
984 
985 
986  // Build a text element for each element in the strlist
987  for (p = lst.begin (); p != lst.end (); p++)
988  {
989  os << "<text ";
990 
991  if (name.compare (p->get_family ()))
992  os << "font-family=\"" << p->get_family () << "\" ";
993 
994  if (weight.compare (p->get_weight ()))
995  os << "font-weight=\"" << p->get_weight () << "\" ";
996 
997  if (angle.compare (p->get_angle ()))
998  os << "font-style=\"" << p->get_angle () << "\" ";
999 
1000  if (size != p->get_size ())
1001  os << "font-size=\"" << p->get_size () << "\" ";
1002 
1003  os << "y=\"" << - p->get_y () << "\" ";
1004 
1005  Matrix col = p->get_color ();
1006  os << "fill=\"rgb(" << col(0)*255 << ","
1007  << col(1)*255 << "," << col(2)*255 << ")\" ";
1008 
1009  // provide an x coordinate for each character in the string
1010  os << "x=\"";
1011  std::vector<double> xdata = p->get_xdata ();
1012  for (auto q = xdata.begin (); q != xdata.end (); q++)
1013  os << (*q) << " ";
1014  os << '"';
1015 
1016  os << '>';
1017 
1018  // translate unicode and special xml characters
1019  if (p->get_code ())
1020  os << "&#" << p->get_code () << ";";
1021  else
1022  {
1023  const std::string str = p->get_string ();
1024  for (auto q = str.begin (); q != str.end (); q++)
1025  {
1026  std::stringstream chr;
1027  chr << *q;
1028  if (chr.str () == "\"")
1029  os << "&quot;";
1030  else if (chr.str () == "'")
1031  os << "&apos;";
1032  else if (chr.str () == "&")
1033  os << "&amp;";
1034  else if (chr.str () == "<")
1035  os << "&lt;";
1036  else if (chr.str () == ">")
1037  os << "&gt;";
1038  else
1039  os << chr.str ();
1040  }
1041  }
1042  os << "</text>";
1043  }
1044  os << "</g>";
1045 
1046  return os.str ();
1047 }
1048 
1049 std::string
1050 gl2ps_renderer::strlist_to_ps (double x, double y, double z,
1051  Matrix box, double rotation,
1052  std::list<text_renderer::string>& lst)
1053 {
1054  if (lst.empty ())
1055  return "";
1056  else if (lst.size () == 1)
1057  {
1058  static bool warned = false;
1059  // This may be an svg image, not handled in native eps format.
1060  if (! lst.front ().get_svg_element ().empty ())
1061  {
1062  if (! warned)
1063  {
1064  warned = true;
1065  warning_with_id ("Octave:print:unhandled-svg-content",
1066  "print: unhandled LaTeX strings. "
1067  "Use -svgconvert option or -d*latex* output "
1068  "device.");
1069  }
1070  return "";
1071  }
1072  }
1073 
1074  // Translate and rotate coordinates in order to use bottom-left alignment
1075  fix_strlist_position (x, y, z, box, rotation, lst);
1076  Matrix prev_color (1, 3, -1);
1077 
1078  std::ostringstream ss;
1079  ss << "gsave\n";
1080 
1081  static bool warned = false;
1082 
1083  for (const auto& txtobj : lst)
1084  {
1085  // Color
1086  if (txtobj.get_color () != prev_color)
1087  {
1088  prev_color = txtobj.get_color ();
1089  for (int i = 0; i < 3; i++)
1090  ss << prev_color(i) << " ";
1091 
1092  ss << "C\n";
1093  }
1094 
1095  // String
1096  std::string str;
1097  if (txtobj.get_code ())
1098  {
1099  m_fontname = "Symbol";
1100  str = code_to_symbol (txtobj.get_code ());
1101  }
1102  else
1103  {
1104  m_fontname = select_font (txtobj.get_name (),
1105  txtobj.get_weight () == "bold",
1106  txtobj.get_angle () == "italic");
1107 
1108  // Check that the string is composed of single byte characters
1109  const std::string tmpstr = txtobj.get_string ();
1110  const uint8_t *c
1111  = reinterpret_cast<const uint8_t *> (tmpstr.c_str ());
1112 
1113  for (std::size_t i = 0; i < tmpstr.size ();)
1114  {
1115  int mblen = octave_u8_strmblen_wrapper (c + i);
1116 
1117  // Replace multibyte or non ascii characters by a question mark
1118  if (mblen > 1)
1119  {
1120  str += "?";
1121  if (! warned)
1122  {
1123  warning_with_id ("Octave:print:unsupported-multibyte",
1124  "print: only ASCII characters are "
1125  "supported for EPS and derived "
1126  "formats. Use the '-svgconvert' "
1127  "option for better font support.");
1128  warned = true;
1129  }
1130  }
1131  else if (mblen < 1)
1132  {
1133  mblen = 1;
1134  str += "?";
1135  if (! warned)
1136  {
1137  warning_with_id ("Octave:print:unhandled-character",
1138  "print: only ASCII characters are "
1139  "supported for EPS and derived "
1140  "formats. Use the '-svgconvert' "
1141  "option for better font support.");
1142  warned = true;
1143  }
1144  }
1145  else
1146  str += tmpstr.at (i);
1147 
1148  i += mblen;
1149  }
1150  }
1151 
1152  escape_character ("\\", str);
1153  escape_character ("(", str);
1154  escape_character (")", str);
1155 
1156  ss << "(" << str << ") [";
1157 
1158  std::vector<double> xdata = txtobj.get_xdata ();
1159  for (std::size_t i = 1; i < xdata.size (); i++)
1160  ss << xdata[i] - xdata[i-1] << " ";
1161 
1162  ss << "10] " << rotation << " " << txtobj.get_x ()
1163  << " " << txtobj.get_y () << " " << txtobj.get_size ()
1164  << " /" << m_fontname << " SRX\n";
1165  }
1166 
1167  ss << "grestore\n";
1168 
1169  return ss.str ();
1170 }
1171 
1172 Matrix
1173 gl2ps_renderer::render_text (const std::string& txt,
1174  double x, double y, double z,
1175  int ha, int va, double rotation)
1176 {
1177  std::string saved_font = m_fontname;
1178 
1179  if (txt.empty ())
1180  return Matrix (1, 4, 0.0);
1181 
1182  Matrix bbox;
1183  std::string str = txt;
1184  std::list<text_renderer::string> lst;
1185 
1186  text_to_strlist (str, lst, bbox, ha, va, rotation);
1187  m_glfcns.glRasterPos3d (x, y, z);
1188 
1189  // For svg/eps directly dump a preformated text element into gl2ps output
1190  if (m_term.find ("svg") != std::string::npos)
1191  {
1192  std::string elt = strlist_to_svg (x, y, z, bbox, rotation, lst);
1193  if (! elt.empty ())
1194  gl2psSpecial (GL2PS_SVG, elt.c_str ());
1195  }
1196  else if (m_term.find ("eps") != std::string::npos)
1197  {
1198  std::string elt = strlist_to_ps (x, y, z, bbox, rotation, lst);
1199  if (! elt.empty ())
1200  gl2psSpecial (GL2PS_EPS, elt.c_str ());
1201 
1202  }
1203  else
1204  gl2psTextOpt (str.c_str (), m_fontname.c_str (), m_fontsize,
1205  alignment_to_mode (ha, va), rotation);
1206 
1207  m_fontname = saved_font;
1208 
1209  return bbox;
1210 }
1211 
1212 void
1213 gl2ps_renderer::set_font (const base_properties& props)
1214 {
1215  opengl_renderer::set_font (props);
1216 
1217  // Set the interpreter so that text_to_pixels can parse strings properly
1218  if (props.has_property ("interpreter"))
1219  set_interpreter (props.get ("interpreter").string_value ());
1220 
1221  m_fontsize = props.get ("__fontsize_points__").double_value ();
1222 
1223  caseless_str fn = props.get ("fontname").xtolower ().string_value ();
1224  bool isbold
1225  =(props.get ("fontweight").xtolower ().string_value () == "bold");
1226  bool isitalic
1227  = (props.get ("fontangle").xtolower ().string_value () == "italic");
1228 
1229  m_fontname = select_font (fn, isbold, isitalic);
1230 }
1231 
1232 void
1233 gl2ps_renderer::draw_image (const image::properties& props)
1234 {
1235  octave_value cdata = props.get_color_data ();
1236  dim_vector dv (cdata.dims ());
1237  int h = dv(0);
1238  int w = dv(1);
1239 
1240  Matrix x = props.get_xdata ().matrix_value ();
1241  Matrix y = props.get_ydata ().matrix_value ();
1242 
1243  // Someone wants us to draw an empty image? No way.
1244  if (x.isempty () || y.isempty ())
1245  return;
1246 
1247  // Sort x/ydata and mark flipped dimensions
1248  bool xflip = false;
1249  if (x(0) > x(1))
1250  {
1251  std::swap (x(0), x(1));
1252  xflip = true;
1253  }
1254  else if (w > 1 && x(1) == x(0))
1255  x(1) = x(1) + (w-1);
1256 
1257  bool yflip = false;
1258  if (y(0) > y(1))
1259  {
1260  std::swap (y(0), y(1));
1261  yflip = true;
1262  }
1263  else if (h > 1 && y(1) == y(0))
1264  y(1) = y(1) + (h-1);
1265 
1266 
1267  const ColumnVector p0 = m_xform.transform (x(0), y(0), 0);
1268  const ColumnVector p1 = m_xform.transform (x(1), y(1), 0);
1269 
1270  if (math::isnan (p0(0)) || math::isnan (p0(1))
1271  || math::isnan (p1(0)) || math::isnan (p1(1)))
1272  {
1273  warning ("opengl_renderer: image X,Y data too large to draw");
1274  return;
1275  }
1276 
1277  // image pixel size in screen pixel units
1278  float pix_dx, pix_dy;
1279  // image pixel size in normalized units
1280  float nor_dx, nor_dy;
1281 
1282  if (w > 1)
1283  {
1284  pix_dx = (p1(0) - p0(0)) / (w-1);
1285  nor_dx = (x(1) - x(0)) / (w-1);
1286  }
1287  else
1288  {
1289  const ColumnVector p1w = m_xform.transform (x(1) + 1, y(1), 0);
1290  pix_dx = p1w(0) - p0(0);
1291  nor_dx = 1;
1292  }
1293 
1294  if (h > 1)
1295  {
1296  pix_dy = (p1(1) - p0(1)) / (h-1);
1297  nor_dy = (y(1) - y(0)) / (h-1);
1298  }
1299  else
1300  {
1301  const ColumnVector p1h = m_xform.transform (x(1), y(1) + 1, 0);
1302  pix_dy = p1h(1) - p0(1);
1303  nor_dy = 1;
1304  }
1305 
1306  // OpenGL won't draw any of the image if its origin is outside the
1307  // viewport/clipping plane so we must do the clipping ourselves.
1308 
1309  int j0, j1, jj, i0, i1, ii;
1310  j0 = 0, j1 = w;
1311  i0 = 0, i1 = h;
1312 
1313  float im_xmin = x(0) - nor_dx/2;
1314  float im_xmax = x(1) + nor_dx/2;
1315  float im_ymin = y(0) - nor_dy/2;
1316  float im_ymax = y(1) + nor_dy/2;
1317 
1318  // Clip to axes or viewport
1319  bool do_clip = props.is_clipping ();
1320  Matrix vp = get_viewport_scaled ();
1321 
1322  ColumnVector vp_lim_min
1323  = m_xform.untransform (std::numeric_limits <float>::epsilon (),
1324  std::numeric_limits <float>::epsilon ());
1325  ColumnVector vp_lim_max = m_xform.untransform (vp(2), vp(3));
1326 
1327  if (vp_lim_min(0) > vp_lim_max(0))
1328  std::swap (vp_lim_min(0), vp_lim_max(0));
1329 
1330  if (vp_lim_min(1) > vp_lim_max(1))
1331  std::swap (vp_lim_min(1), vp_lim_max(1));
1332 
1333  float clip_xmin
1334  = do_clip ? (vp_lim_min(0) > m_xmin ? vp_lim_min(0) : m_xmin)
1335  : vp_lim_min(0);
1336 
1337  float clip_ymin
1338  = do_clip ? (vp_lim_min(1) > m_ymin ? vp_lim_min(1) : m_ymin)
1339  : vp_lim_min(1);
1340 
1341  float clip_xmax
1342  = do_clip ? (vp_lim_max(0) < m_xmax ? vp_lim_max(0) : m_xmax)
1343  : vp_lim_max(0);
1344 
1345  float clip_ymax
1346  = do_clip ? (vp_lim_max(1) < m_ymax ? vp_lim_max(1) : m_ymax)
1347  : vp_lim_max(1);
1348 
1349  if (im_xmin < clip_xmin)
1350  j0 += (clip_xmin - im_xmin)/nor_dx + 1;
1351 
1352  if (im_xmax > clip_xmax)
1353  j1 -= (im_xmax - clip_xmax)/nor_dx;
1354 
1355  if (im_ymin < clip_ymin)
1356  i0 += (clip_ymin - im_ymin)/nor_dy + 1;
1357 
1358  if (im_ymax > clip_ymax)
1359  i1 -= (im_ymax - clip_ymax)/nor_dy;
1360 
1361  if (i0 >= i1 || j0 >= j1)
1362  return;
1363 
1364  float zoom_x;
1365  m_glfcns.glGetFloatv (GL_ZOOM_X, &zoom_x);
1366  float zoom_y;
1367  m_glfcns.glGetFloatv (GL_ZOOM_Y, &zoom_y);
1368 
1369  m_glfcns.glPixelZoom (m_devpixratio * pix_dx, - m_devpixratio * pix_dy);
1370  m_glfcns.glRasterPos3d (im_xmin + nor_dx*j0, im_ymin + nor_dy*i0, 0);
1371 
1372  // Expect RGB data
1373  if (dv.ndims () == 3 && dv(2) == 3)
1374  {
1375  if (cdata.is_double_type ())
1376  {
1377  const NDArray xcdata = cdata.array_value ();
1378 
1379  OCTAVE_LOCAL_BUFFER (GLfloat, a,
1380  static_cast<size_t> (3)*(j1-j0)*(i1-i0));
1381 
1382  for (int i = i0; i < i1; i++)
1383  {
1384  for (int j = j0, idx = (i-i0)*(j1-j0)*3; j < j1; j++, idx += 3)
1385  {
1386  if (! yflip)
1387  ii = i;
1388  else
1389  ii = h - i - 1;
1390 
1391  if (! xflip)
1392  jj = j;
1393  else
1394  jj = w - j - 1;
1395 
1396  a[idx] = xcdata(ii, jj, 0);
1397  a[idx+1] = xcdata(ii, jj, 1);
1398  a[idx+2] = xcdata(ii, jj, 2);
1399  }
1400  }
1401 
1402  draw_pixels (j1-j0, i1-i0, a);
1403 
1404  }
1405  else if (cdata.is_single_type ())
1406  {
1407  const FloatNDArray xcdata = cdata.float_array_value ();
1408 
1409  OCTAVE_LOCAL_BUFFER (GLfloat, a,
1410  static_cast<size_t> (3)*(j1-j0)*(i1-i0));
1411 
1412  for (int i = i0; i < i1; i++)
1413  {
1414  for (int j = j0, idx = (i-i0)*(j1-j0)*3; j < j1; j++, idx += 3)
1415  {
1416  if (! yflip)
1417  ii = i;
1418  else
1419  ii = h - i - 1;
1420 
1421  if (! xflip)
1422  jj = j;
1423  else
1424  jj = w - j - 1;
1425 
1426  a[idx] = xcdata(ii, jj, 0);
1427  a[idx+1] = xcdata(ii, jj, 1);
1428  a[idx+2] = xcdata(ii, jj, 2);
1429  }
1430  }
1431 
1432  draw_pixels (j1-j0, i1-i0, a);
1433 
1434  }
1435  else if (cdata.is_uint8_type ())
1436  {
1437  const uint8NDArray xcdata = cdata.uint8_array_value ();
1438 
1439  OCTAVE_LOCAL_BUFFER (GLubyte, a,
1440  static_cast<size_t> (3)*(j1-j0)*(i1-i0));
1441 
1442  for (int i = i0; i < i1; i++)
1443  {
1444  for (int j = j0, idx = (i-i0)*(j1-j0)*3; j < j1; j++, idx += 3)
1445  {
1446  if (! yflip)
1447  ii = i;
1448  else
1449  ii = h - i - 1;
1450 
1451  if (! xflip)
1452  jj = j;
1453  else
1454  jj = w - j - 1;
1455 
1456  a[idx] = xcdata(ii, jj, 0);
1457  a[idx+1] = xcdata(ii, jj, 1);
1458  a[idx+2] = xcdata(ii, jj, 2);
1459  }
1460  }
1461 
1462  draw_pixels (j1-j0, i1-i0, a);
1463 
1464  }
1465  else if (cdata.is_uint16_type ())
1466  {
1467  const uint16NDArray xcdata = cdata.uint16_array_value ();
1468 
1469  OCTAVE_LOCAL_BUFFER (GLushort, a,
1470  static_cast<size_t> (3)*(j1-j0)*(i1-i0));
1471 
1472  for (int i = i0; i < i1; i++)
1473  {
1474  for (int j = j0, idx = (i-i0)*(j1-j0)*3; j < j1; j++, idx += 3)
1475  {
1476  if (! yflip)
1477  ii = i;
1478  else
1479  ii = h - i - 1;
1480 
1481  if (! xflip)
1482  jj = j;
1483  else
1484  jj = w - j - 1;
1485 
1486  a[idx] = xcdata(ii, jj, 0);
1487  a[idx+1] = xcdata(ii, jj, 1);
1488  a[idx+2] = xcdata(ii, jj, 2);
1489  }
1490  }
1491 
1492  draw_pixels (j1-j0, i1-i0, a);
1493 
1494  }
1495  else
1496  warning ("opengl_renderer: invalid image data type (expected double, single, uint8, or uint16)");
1497 
1498  m_glfcns.glPixelZoom (zoom_x, zoom_y);
1499 
1500  }
1501 }
1502 
1503 void
1504 gl2ps_renderer::draw_pixels (int w, int h, const float *data)
1505 {
1506  // Clip data between 0 and 1 for float values
1507  OCTAVE_LOCAL_BUFFER (float, tmp_data, static_cast<size_t> (3)*w*h);
1508 
1509  for (int i = 0; i < 3*h*w; i++)
1510  tmp_data[i] = (data[i] < 0.0f ? 0.0f : (data[i] > 1.0f ? 1.0f : data[i]));
1511 
1512  gl2psDrawPixels (w, h, 0, 0, GL_RGB, GL_FLOAT, tmp_data);
1513 }
1514 
1515 void
1516 gl2ps_renderer::draw_pixels (int w, int h, const uint8_t *data)
1517 {
1518  // gl2psDrawPixels only supports the GL_FLOAT type.
1519 
1520  OCTAVE_LOCAL_BUFFER (float, tmp_data, static_cast<size_t> (3)*w*h);
1521 
1522  static const float maxval = std::numeric_limits<uint8_t>::max ();
1523 
1524  for (int i = 0; i < 3*w*h; i++)
1525  tmp_data[i] = data[i] / maxval;
1526 
1527  draw_pixels (w, h, tmp_data);
1528 }
1529 
1530 void
1531 gl2ps_renderer::draw_pixels (int w, int h, const uint16_t *data)
1532 {
1533  // gl2psDrawPixels only supports the GL_FLOAT type.
1534 
1535  OCTAVE_LOCAL_BUFFER (float, tmp_data, static_cast<size_t> (3)*w*h);
1536 
1537  static const float maxval = std::numeric_limits<uint16_t>::max ();
1538 
1539  for (int i = 0; i < 3*w*h; i++)
1540  tmp_data[i] = data[i] / maxval;
1541 
1542  draw_pixels (w, h, tmp_data);
1543 }
1544 
1545 void
1546 gl2ps_renderer::draw_text (const text::properties& props)
1547 {
1548  if (props.get_string ().isempty ())
1549  return;
1550 
1551  draw_text_background (props, true);
1552 
1553  // First set font properties: freetype will use them to compute
1554  // coordinates and gl2ps will retrieve the color directly from the
1555  // feedback buffer
1556  set_font (props);
1557  set_color (props.get_color_rgb ());
1558 
1559  std::string saved_font = m_fontname;
1560 
1561  // Alignment
1562  int halign = 0;
1563  int valign = 0;
1564 
1565  if (props.horizontalalignment_is ("center"))
1566  halign = 1;
1567  else if (props.horizontalalignment_is ("right"))
1568  halign = 2;
1569 
1570  if (props.verticalalignment_is ("top"))
1571  valign = 2;
1572  else if (props.verticalalignment_is ("baseline"))
1573  valign = 3;
1574  else if (props.verticalalignment_is ("middle"))
1575  valign = 1;
1576 
1577  // FIXME: handle margin and surrounding box
1578  // Matrix bbox;
1579 
1580  const Matrix pos = get_transform ().scale (props.get_data_position ());
1581  std::string str = props.get_string ().string_vector_value ().join ("\n");
1582 
1583  render_text (str, pos(0), pos(1), pos.numel () > 2 ? pos(2) : 0.0,
1584  halign, valign, props.get_rotation ());
1585 }
1586 
1587 OCTAVE_END_NAMESPACE(octave)
1588 
1589 #endif
1590 
1591 OCTAVE_BEGIN_NAMESPACE(octave)
1592 
1593 // If the name of the stream begins with '|', open a pipe to the command
1594 // named by the rest of the string. Otherwise, write to the named file.
1595 
1596 void
1597 gl2ps_print (opengl_functions& glfcns, const graphics_object& fig,
1598  const std::string& stream, const std::string& term)
1599 {
1600 #if defined (HAVE_GL2PS_H) && defined (HAVE_OPENGL)
1601 
1602  // FIXME: should we have a way to create a file that begins with the
1603  // character '|'?
1604 
1605  bool have_cmd = stream.length () > 1 && stream[0] == '|';
1606 
1607  FILE *m_fp = nullptr;
1608 
1609  unwind_protect frame;
1610 
1611  if (have_cmd)
1612  {
1613  // Create process and pipe gl2ps output to it.
1614 
1615  std::string cmd = stream.substr (1);
1616 
1617  m_fp = popen (cmd.c_str (), "w");
1618 
1619  if (! m_fp)
1620  error (R"(print: failed to open pipe "%s")", stream.c_str ());
1621 
1622  // Need octave:: qualifier here to avoid ambiguity.
1623  frame.add ([=] () { octave::pclose (m_fp); });
1624  }
1625  else
1626  {
1627  // Write gl2ps output directly to file.
1628 
1629  m_fp = sys::fopen (stream.c_str (), "w");
1630 
1631  if (! m_fp)
1632  error (R"(gl2ps_print: failed to create file "%s")", stream.c_str ());
1633 
1634  frame.add ([=] () { std::fclose (m_fp); });
1635  }
1636 
1637  gl2ps_renderer rend (glfcns, m_fp, term);
1638 
1639  Matrix pos = fig.get ("position").matrix_value ();
1640  rend.set_viewport (pos(2), pos(3));
1641  rend.draw (fig, stream);
1642 
1643  // Make sure buffered commands are finished!!!
1644  rend.finish ();
1645 
1646 #else
1647 
1648  octave_unused_parameter (glfcns);
1649  octave_unused_parameter (fig);
1650  octave_unused_parameter (stream);
1651  octave_unused_parameter (term);
1652 
1653  err_disabled_feature ("gl2ps_print", "gl2ps");
1654 
1655 #endif
1656 }
1657 
1658 OCTAVE_END_NAMESPACE(octave)
#define SEEK_SET
OCTARRAY_OVERRIDABLE_FUNC_API octave_idx_type numel(void) const
Number of elements in the array.
Definition: Array.h:414
Definition: dMatrix.h:42
void add(F &&fcn, Args &&... args)
void protect_var(T &var)
bool is_scalar_type(void) const
Definition: ov.h:789
bool is_double_type(void) const
Definition: ov.h:740
double double_value(bool frc_str_conv=false) const
Definition: ov.h:886
virtual void init_marker(const std::string &m, double size, float width)
Definition: gl-render.cc:4446
virtual void set_linestyle(const std::string &s, bool stipple=false, double linewidth=0.5)
Definition: gl-render.cc:4306
virtual void draw(const graphics_object &go, bool toplevel=true)
Definition: gl-render.cc:714
virtual void draw_axes(const axes::properties &props)
Definition: gl-render.cc:2301
virtual void set_polygon_offset(bool on, float offset=0.0f)
Definition: gl-render.cc:4257
virtual void set_linejoin(const std::string &)
Definition: gl-render.h:112
OCTAVE_BEGIN_NAMESPACE(octave) static octave_value daspk_fcn
T eps(const T &x)
Definition: data.cc:4942
void warning(const char *fmt,...)
Definition: error.cc:1054
void error(const char *fmt,...)
Definition: error.cc:979
std::string tempnam(const std::string &dir, const std::string &pfx)
Definition: file-ops.cc:697
void gl2ps_print(opengl_functions &glfcns, const graphics_object &fig, const std::string &stream, const std::string &term)
ColumnVector transform(const Matrix &m, double x, double y, double z)
Definition: graphics.cc:5819
gh_manager & __get_gh_manager__(void)
Complex atan(const Complex &x)
Definition: lo-mappers.h:71
F77_RET_T const F77_DBLE const F77_DBLE F77_DBLE * d
F77_RET_T const F77_DBLE * x
std::FILE * fopen_tmp(const std::string &name, const std::string &mode)
Definition: lo-sysdep.cc:386
std::string fgets(FILE *f)
Definition: lo-utils.cc:85
static std::string get_temp_directory(void)
T octave_idx_type m
Definition: mx-inlines.cc:773
octave_idx_type n
Definition: mx-inlines.cc:753
std::complex< double > w(std::complex< double > z, double relerr=0)
T::properties & properties(graphics_object obj)
T::size_type strlen(const typename T::value_type *str)
Definition: oct-string.cc:85
OCTAVE_API bool strncmp(const T &str_a, const T &str_b, const typename T::size_type n)
True if the first N characters are the same.
int pipe(int *fildes)
void draw(QDomElement &parent_elt, pdfpainter &painter)
static octave_value box(JNIEnv *jni_env, void *jobj, void *jcls_arg=nullptr)
static uint32_t state[624]
Definition: randmtzig.cc:193
static std::string dir_sep_chars
Definition: shared-fcns.h:96
void respond_to_pending_signals(void)
Definition: sighandlers.cc:110
int octave_ftruncate_wrapper(int fd, off_t sz)