GNU Octave  8.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
latex-text-renderer.cc
Go to the documentation of this file.
1 ////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (C) 2021-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 #ifdef HAVE_CONFIG_H
27 #include <config.h>
28 #endif
29 
30 #include <iostream>
31 #include <fstream>
32 
33 #include "base-text-renderer.h"
34 #include "builtin-defun-decls.h"
35 #include "dim-vector.h"
36 #include "error.h"
37 #include "graphics.h"
38 #include "file-ops.h"
39 #include "interpreter.h"
40 #include "interpreter-private.h"
41 #include "oct-env.h"
42 #include "oct-process.h"
43 
45 
46 std::string
47 quote_string (std::string str)
48 {
49  return ('"' + str + '"');
50 }
51 
52 class
53 OCTINTERP_API
55 {
56 
57 public:
58 
60  : m_fontsize (10.0), m_fontname ("cmr"), m_tmp_dir (),
61  m_color (dim_vector (1, 3), 0), m_latex_binary ("latex"),
62  m_dvipng_binary ("dvipng"), m_dvisvg_binary ("dvisvgm"),
63  m_debug (false), m_testing (true)
64  {
65  std::string bin = sys::env::getenv ("OCTAVE_LATEX_BINARY");
66  if (! bin.empty ())
67  m_latex_binary = quote_string (bin);
68 
69  bin = sys::env::getenv ("OCTAVE_DVIPNG_BINARY");
70  if (! bin.empty ())
71  m_dvipng_binary = quote_string (bin);
72 
73  bin = sys::env::getenv ("OCTAVE_DVISVG_BINARY");
74  if (! bin.empty ())
75  m_dvisvg_binary = quote_string (bin);
76 
77  m_debug = ! sys::env::getenv ("OCTAVE_LATEX_DEBUG_FLAG").empty ();
78  }
79 
81  {
82  if (! m_tmp_dir.empty () && ! m_debug)
83  sys::recursive_rmdir (m_tmp_dir);
84  }
85 
86  void set_font (const std::string& /*name*/, const std::string& /*weight*/,
87  const std::string& /*angle*/, double size)
88  {
89  m_fontsize = size;
90  }
91 
92  void set_color (const Matrix& c)
93  {
94  if (c.numel () == 3)
95  {
96  m_color(0) = static_cast<uint8_t> (c (0) * 255);
97  m_color(1) = static_cast<uint8_t> (c (1) * 255);
98  m_color(2) = static_cast<uint8_t> (c (2) * 255);
99  }
100  }
101 
102  Matrix get_extent (text_element * /*elt*/, double /*rotation*/)
103  {
104  return Matrix (1, 2, 0.0);
105  }
106 
107  Matrix get_extent (const std::string& txt, double rotation,
108  const caseless_str& interpreter)
109  {
110  Matrix bbox;
111  uint8NDArray pixels;
112 
113  text_to_pixels (txt, pixels, bbox, 0, 0, rotation, interpreter, false);
114 
115  return bbox.extract_n (0, 2, 1, 2);
116  }
117 
118  void text_to_strlist (const std::string& txt,
119  std::list<text_renderer::string>& lst,
120  Matrix& bbox, int halign, int valign, double rotation,
121  const caseless_str& interp)
122  {
123  uint8NDArray pixels;
124  text_to_pixels (txt, pixels, bbox, halign, valign, rotation,
125  interp, false);
126 
128  text_renderer::string str ("", fnt, 0.0, 0.0);
129  str.set_color (m_color);
130 
131  gh_manager& gh_mgr = octave::__get_gh_manager__ ();
132 
133  gh_manager::latex_data ldata = gh_mgr.get_latex_data (key (txt, halign));
134 
135  str.set_svg_element (ldata.second);
136 
137  lst.push_back (str);
138  }
139 
140  void text_to_pixels (const std::string& txt, uint8NDArray& pxls,
141  Matrix& bbox, int halign, int valign, double rotation,
142  const caseless_str& interpreter,
143  bool handle_rotation);
144 
145  void set_anti_aliasing (bool /*val*/) { }
146 
147  octave_map get_system_fonts (void) { return octave_map (); }
148 
149  bool ok (void);
150 
151 private:
152 
153  std::string key (const std::string& txt, int halign)
154  {
155  return (txt + ":"
156  + std::to_string (m_fontsize) + ":"
157  + std::to_string (halign) + ":"
158  + std::to_string (m_color(0)) + ":"
159  + std::to_string (m_color(1)) + ":"
160  + std::to_string (m_color(2)));
161  }
162 
163  void warn_helper (std::string caller, std::string txt, std::string cmd,
164  process_execution_result result);
165 
166  uint8NDArray render (const std::string& txt, int halign = 0);
167 
168  bool read_image (const std::string& png_file, uint8NDArray& data) const;
169 
170  std::string write_tex_file (const std::string& txt, int halign);
171 
172 private:
173  double m_fontsize;
174  std::string m_fontname;
175  std::string m_tmp_dir;
177  std::string m_latex_binary;
178  std::string m_dvipng_binary;
179  std::string m_dvisvg_binary;
180  bool m_debug;
181  bool m_testing;
182 
183 };
184 
185 bool
187 {
188  // Only run the test once in a session
189  static bool tested = false;
190 
191  static bool isok = false;
192 
193  if (! tested)
194  {
195  tested = true;
196 
197  // For testing, render a questoin mark
198  uint8NDArray pixels = render ("?");
199 
200  if (! pixels.isempty ())
201  isok = true;
202  else
203  warning_with_id ("Octave:LaTeX:internal-error",
204  "latex_renderer: a run-time test failed and the 'latex' interpreter has been disabled.");
205  }
206 
207  m_testing = false;
208 
209  return isok;
210 }
211 
212 std::string
213 latex_renderer::write_tex_file (const std::string& txt, int halign)
214 {
215  if (m_tmp_dir.empty ())
216  {
217  //Create the temporary directory
218 #if defined (OCTAVE_USE_WINDOWS_API)
219  static std::string base_tmp_dir;
220 
221  if (base_tmp_dir.empty ())
222  {
223  base_tmp_dir = sys::env::get_temp_directory ();
224 
225  // Make sure we don't get short 8.3 path on Windows since some
226  // versions of latex on that platform don't support them
227  // (see bug #62779)
228  if (base_tmp_dir.find ('~') != std::string::npos)
229  base_tmp_dir = sys::canonicalize_file_name (base_tmp_dir);
230  }
231 
232  m_tmp_dir = sys::tempnam (base_tmp_dir, "latex");
233 #else
234  m_tmp_dir = sys::tempnam ("", "latex");
235 #endif
236 
237  if (sys::mkdir (m_tmp_dir, 0700) != 0)
238  {
239  warning_with_id ("Octave:LaTeX:internal-error",
240  "latex_renderer: unable to create temp directory");
241  return std::string ();
242  }
243  }
244 
245  std::string base_file_name
246  = sys::file_ops::concat (m_tmp_dir, "default");
247 
248  // Duplicate \n characters and align multi-line strings based on
249  // horizontalalignment
250  std::string latex_txt (txt);
251  std::size_t pos = 0;
252 
253  while (true)
254  {
255  pos = txt.find_first_of ("\n", pos);
256 
257  if (pos == std::string::npos)
258  break;
259 
260  latex_txt.replace (pos, 1, "\n\n");
261 
262  pos += 1;
263  }
264 
265  std::string env ("flushleft");
266  if (halign == 1)
267  env = "center";
268  else if (halign == 2)
269  env = "flushright";
270 
271  latex_txt = std::string ("\\begin{" ) + env + "}\n"
272  + latex_txt + "\n"
273  + "\\end{" + env + "}\n";
274 
275  // Write to temporary .tex file
276  std::ofstream file;
277  file.open (base_file_name + ".tex");
278  file << "\\documentclass[10pt, varwidth]{standalone}\n"
279  << "\\usepackage{amsmath}\n"
280  << "\\usepackage[utf8]{inputenc}\n"
281  << "\\begin{document}\n"
282  << latex_txt << "\n"
283  << "\\end{document}";
284  file.close ();
285 
286  return base_file_name;
287 }
288 
289 bool
290 latex_renderer::read_image (const std::string& png_file,
291  uint8NDArray& data) const
292 {
293  uint8NDArray alpha;
294  uint8NDArray rgb;
295  int height;
296  int width;
297 
298  try
299  {
300  // First get the image size to build the argument to __magick_read__
301  octave_value_list retval = F__magick_ping__ (ovl (png_file), 1);
302 
303  octave_scalar_map info
304  = retval(0).xscalar_map_value ("latex_renderer::read_image: "
305  "Wrong type for info");
306  height = info.getfield ("rows").int_value ();
307  width = info.getfield ("columns").int_value ();
308  Cell region (dim_vector(1, 2));
309  region(0) = range<double> (1.0, height);
310  region(1) = range<double> (1.0, width);
311  info.setfield ("region", region);
312  info.setfield ("index", octave_value (1));
313 
314  // Retrieve the alpha map
315  retval = F__magick_read__ (ovl (png_file, info), 3);
316 
317  alpha = retval(2).xuint8_array_value ("latex_renderer::read_image: "
318  "Wrong type for alpha");
319  }
320  catch (const execution_exception& ee)
321  {
322  warning_with_id ("Octave:LaTeX:internal-error",
323  "latex_renderer:: failed to read png data. %s",
324  ee.message ().c_str ());
325 
326  interpreter& interp = __get_interpreter__ ();
327 
328  interp.recover_from_exception ();
329 
330  return false;
331  }
332 
333  data = uint8NDArray (dim_vector (4, width, height),
334  static_cast<uint8_t> (0));
335 
336  for (int i = 0; i < height; i++)
337  {
338  for (int j = 0; j < width; j++)
339  {
340  data(0, j, i) = m_color(0);
341  data(1, j, i) = m_color(1);
342  data(2, j, i) = m_color(2);
343  data(3, j, i) = alpha(height-i-1, j);
344  }
345  }
346 
347  return true;
348 }
349 
350 void
351 latex_renderer::warn_helper (std::string caller, std::string txt,
352  std::string cmd, process_execution_result result)
353 {
354  if (m_testing && ! m_debug)
355  return;
356 
357  if (! m_debug)
358  warning_with_id ("Octave:LaTeX:internal-error",
359  "latex_renderer: unable to compile \"%s\"",
360  txt.c_str ());
361  else
362  warning_with_id ("Octave:LaTeX:internal-error",
363  "latex_renderer: %s failed for string \"%s\"\n\
364 * Command:\n\t%s\n\n* Error:\n%s\n\n* Stdout:\n%s",
365  caller.c_str (), txt.c_str (), cmd.c_str (),
366  result.err_msg ().c_str (),
367  result.stdout_output ().c_str ());
368 }
369 
371 latex_renderer::render (const std::string& txt, int halign)
372 {
373  // Render if it was not already done
374  gh_manager& gh_mgr = octave::__get_gh_manager__ ();
375 
376  gh_manager::latex_data ldata = gh_mgr.get_latex_data (key (txt, halign));
377 
378  if (! ldata.first.isempty ())
379  return ldata.first;
380 
381  uint8NDArray data;
382 
383  // First write the base .tex file
384  std::string base_file_name = write_tex_file (txt, halign);
385 
386  if (base_file_name.empty ())
387  return data;
388 
389  // Generate DVI file
390  std::string tex_file = quote_string (base_file_name + ".tex");
391  std::string dvi_file = quote_string (base_file_name + ".dvi");
392  std::string log_file = quote_string (base_file_name + ".log");
393 
395  std::string cmd = (m_latex_binary + " -interaction=nonstopmode "
396  + "-output-directory=" + quote_string (m_tmp_dir) + " "
397  + tex_file);
398 
399 #if defined (OCTAVE_USE_WINDOWS_API)
400  cmd = quote_string (cmd);
401 #endif
402 
403  result = run_command_and_return_output (cmd);
404 
405  if (result.exit_status () != 0)
406  {
407  warn_helper ("latex", txt, cmd, result);
408 
409  if (txt != "?")
410  {
411  write_tex_file ("?", halign);
412 
413  result = run_command_and_return_output (cmd);
414  if (result.exit_status () != 0)
415  return data;
416  }
417  else
418  return data;
419  }
420 
421  double size_factor = m_fontsize / 10.0;
422 
423 
424  // Convert DVI to SVG, read file and store its content for later use in
425  // gl2ps_print
426  std::string svg_file = base_file_name + ".svg";
427 
428  cmd = (m_dvisvg_binary + " -n "
429  + "-TS" + std::to_string (size_factor) + " "
430  + "-v1 -o " + quote_string (svg_file) + " "
431  + dvi_file);
432 
433 #if defined (OCTAVE_USE_WINDOWS_API)
434  cmd = quote_string (cmd);
435 #endif
436 
437  result = run_command_and_return_output (cmd);
438 
439  if (result.exit_status () != 0)
440  {
441  warn_helper ("dvisvg", txt, cmd, result);
442  return data;
443  }
444 
445  std::ifstream svg_stream (svg_file);
446  std::string svg_string;
447  svg_string.assign (std::istreambuf_iterator<char> (svg_stream),
448  std::istreambuf_iterator<char> ());
449 
450  // Convert DVI to PNG, read file and format pixel data for later use in
451  // OpenGL
452  std::string png_file = base_file_name + ".png";
453 
454  cmd = (m_dvipng_binary + " " + dvi_file + " "
455  + "-q -o " + quote_string (png_file) + " "
456  + "-bg Transparent -D "
457  + std::to_string (std::floor (72.0 * size_factor)));
458 
459 #if defined (OCTAVE_USE_WINDOWS_API)
460  cmd = quote_string (cmd);
461 #endif
462 
463  result = run_command_and_return_output (cmd);
464 
465  if (result.exit_status () != 0)
466  {
467  warn_helper ("dvipng", txt, cmd, result);
468  return data;
469  }
470 
471  if (! read_image (png_file, data))
472  return data;
473 
474  // Cache pixel and svg data for this string
475  ldata.first = data;
476  ldata.second = svg_string;
477 
478  gh_mgr.set_latex_data (key (txt, halign), ldata);
479 
480  if (m_debug)
481  std::cout << "* Caching " << key (txt, halign) << std::endl;
482 
483  return data;
484 }
485 
486 void
487 latex_renderer::text_to_pixels (const std::string& txt, uint8NDArray& pixels,
488  Matrix& bbox, int halign, int valign,
489  double rotation,
490  const caseless_str& /*interpreter*/,
491  bool handle_rotation)
492 {
493  // Return early for empty strings
494  if (txt.empty ())
495  {
496  bbox = Matrix (1, 4, 0.0);
497  return;
498  }
499 
500  if (ok ())
501  pixels = render (txt, halign);
502  else
503  pixels = uint8NDArray (dim_vector (4, 1, 1), static_cast<uint8_t> (0));
504 
505  if (pixels.ndims () < 3 || pixels.isempty ())
506  return; // nothing to render
507 
508  // Store unrotated bbox size
509  bbox = Matrix (1, 4, 0.0);
510  bbox (2) = pixels.dim2 ();
511  bbox (3) = pixels.dim3 ();
512 
513  // Now rotate pixels if necessary
514  int rot_mode = rotation_to_mode (rotation);
515 
516  if (! pixels.isempty ())
517  rotate_pixels (pixels, rot_mode);
518 
519  // Move X0 and Y0 depending on alignments and eventually swap values
520  // for text rotated 90° 180° or 270°
521  fix_bbox_anchor (bbox, halign, valign, rot_mode, handle_rotation);
522 }
523 
526 {
527  latex_renderer *renderer = new latex_renderer ();
528 
529  return renderer;
530 }
531 
OCTAVE_END_NAMESPACE(octave)
ComplexNDArray concat(NDArray &ra, ComplexNDArray &rb, const Array< octave_idx_type > &ra_idx)
Definition: CNDArray.cc:418
OCTAVE_EXPORT octave_value_list F__magick_read__(const octave_value_list &args, int nargout)
OCTAVE_EXPORT octave_value_list F__magick_ping__(const octave_value_list &args, int)
OCTARRAY_OVERRIDABLE_FUNC_API bool isempty(void) const
Size of the specified dimension.
Definition: Array.h:651
OCTARRAY_OVERRIDABLE_FUNC_API octave_idx_type dim2(void) const
Definition: Array.h:467
OCTARRAY_OVERRIDABLE_FUNC_API octave_idx_type numel(void) const
Number of elements in the array.
Definition: Array.h:414
OCTARRAY_OVERRIDABLE_FUNC_API octave_idx_type dim3(void) const
Size of the specified dimension.
Definition: Array.h:479
OCTARRAY_OVERRIDABLE_FUNC_API int ndims(void) const
Size of the specified dimension.
Definition: Array.h:677
Definition: Cell.h:43
Definition: dMatrix.h:42
OCTAVE_API Matrix extract_n(octave_idx_type r1, octave_idx_type c1, octave_idx_type nr, octave_idx_type nc) const
Definition: dMatrix.cc:407
void fix_bbox_anchor(Matrix &bbox, int halign, int valign, int rot_mode, bool handle_rotation) const
void rotate_pixels(uint8NDArray &pixels, int rot_mode) const
int rotation_to_mode(double rotation) const
Vector representing the dimensions (size) of an Array.
Definition: dim-vector.h:94
Definition: oct-env.h:40
void recover_from_exception(void)
std::string m_dvipng_binary
std::string key(const std::string &txt, int halign)
octave_map get_system_fonts(void)
bool read_image(const std::string &png_file, uint8NDArray &data) const
void text_to_strlist(const std::string &txt, std::list< text_renderer::string > &lst, Matrix &bbox, int halign, int valign, double rotation, const caseless_str &interp)
Matrix get_extent(text_element *, double)
std::string m_latex_binary
void text_to_pixels(const std::string &txt, uint8NDArray &pxls, Matrix &bbox, int halign, int valign, double rotation, const caseless_str &interpreter, bool handle_rotation)
Matrix get_extent(const std::string &txt, double rotation, const caseless_str &interpreter)
std::string m_dvisvg_binary
void set_font(const std::string &, const std::string &, const std::string &, double size)
void warn_helper(std::string caller, std::string txt, std::string cmd, process_execution_result result)
void set_anti_aliasing(bool)
std::string write_tex_file(const std::string &txt, int halign)
void set_color(const Matrix &c)
uint8NDArray render(const std::string &txt, int halign=0)
void setfield(const std::string &key, const octave_value &val)
Definition: oct-map.cc:190
octave_value getfield(const std::string &key) const
Definition: oct-map.cc:183
int int_value(bool req_int=false, bool frc_str_conv=false) const
Definition: ov.h:857
int exit_status(void) const
Definition: oct-process.h:59
std::string stdout_output(void) const
Definition: oct-process.h:63
std::string err_msg(void) const
Definition: oct-process.h:61
void set_svg_element(const std::string &svg)
void set_color(const uint8NDArray &c)
OCTAVE_BEGIN_NAMESPACE(octave) static octave_value daspk_fcn
void warning_with_id(const char *id, const char *fmt,...)
Definition: error.cc:1069
std::string canonicalize_file_name(const std::string &name)
Definition: file-ops.cc:744
std::string tempnam(const std::string &dir, const std::string &pfx)
Definition: file-ops.cc:697
int recursive_rmdir(const std::string &name)
Definition: file-ops.cc:608
int mkdir(const std::string &nm, mode_t md)
Definition: file-ops.cc:402
interpreter & __get_interpreter__(void)
gh_manager & __get_gh_manager__(void)
base_text_renderer * make_latex_text_renderer(void)
std::string quote_string(std::string str)
std::complex< T > floor(const std::complex< T > &x)
Definition: lo-mappers.h:130
std::ofstream ofstream(const std::string &filename, const std::ios::openmode mode)
Definition: lo-sysdep.cc:438
std::ifstream ifstream(const std::string &filename, const std::ios::openmode mode)
Definition: lo-sysdep.cc:424
static std::string get_temp_directory(void)
class OCTAVE_API Matrix
Definition: mx-fwd.h:31
process_execution_result run_command_and_return_output(const std::string &cmd_str)
Definition: oct-process.cc:51
octave_value_list ovl(const OV_Args &... args)
Construct an octave_value_list with less typing.
Definition: ovl.h:211
intNDArray< octave_uint8 > uint8NDArray
Definition: uint8NDArray.h:36