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