GNU Octave 7.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-2022 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
44namespace octave
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,
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__ ("text_to_strlist");
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,
143 bool handle_rotation);
144
145 void set_anti_aliasing (bool /*val*/) { }
146
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,
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:
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;
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 m_tmp_dir = sys::tempnam ("", "latex");
219
220 if (sys::mkdir (m_tmp_dir, 0700) != 0)
221 {
222 warning_with_id ("Octave:LaTeX:internal-error",
223 "latex_renderer: unable to create temp directory");
224 return std::string ();
225 }
226 }
227
228 std::string base_file_name
229 = sys::file_ops::concat (m_tmp_dir, "default");
230
231 // Duplicate \n characters and align multi-line strings based on
232 // horizontalalignment
233 std::string latex_txt (txt);
234 std::size_t pos = 0;
235
236 while (true)
237 {
238 pos = txt.find_first_of ("\n", pos);
239
240 if (pos == std::string::npos)
241 break;
242
243 latex_txt.replace (pos, 1, "\n\n");
244
245 pos += 1;
246 }
247
248 std::string env ("flushleft");
249 if (halign == 1)
250 env = "center";
251 else if (halign == 2)
252 env = "flushright";
253
254 latex_txt = std::string ("\\begin{" ) + env + "}\n"
255 + latex_txt + "\n"
256 + "\\end{" + env + "}\n";
257
258 // Write to temporary .tex file
259 std::ofstream file;
260 file.open (base_file_name + ".tex");
261 file << "\\documentclass[10pt, varwidth]{standalone}\n"
262 << "\\usepackage{amsmath}\n"
263 << "\\usepackage[utf8]{inputenc}\n"
264 << "\\begin{document}\n"
265 << latex_txt << "\n"
266 << "\\end{document}";
267 file.close ();
268
269 return base_file_name;
270 }
271
272 bool
273 latex_renderer::read_image (const std::string& png_file,
274 uint8NDArray& data) const
275 {
276 uint8NDArray alpha;
277 uint8NDArray rgb;
278 int height;
279 int width;
280
281 try
282 {
283 // First get the image size to build the argument to __magick_read__
284 octave_value_list retval = F__magick_ping__ (ovl (png_file), 1);
285
287 = retval(0).xscalar_map_value ("latex_renderer::read_image: "
288 "Wrong type for info");
289 height = info.getfield ("rows").int_value ();
290 width = info.getfield ("columns").int_value ();
291 Cell region (dim_vector(1, 2));
292 region(0) = range<double> (1.0, height);
293 region(1) = range<double> (1.0, width);
294 info.setfield ("region", region);
295 info.setfield ("index", octave_value (1));
296
297 // Retrieve the alpha map
298 retval = F__magick_read__ (ovl (png_file, info), 3);
299
300 alpha = retval(2).xuint8_array_value ("latex_renderer::read_image: "
301 "Wrong type for alpha");
302 }
303 catch (const execution_exception& ee)
304 {
305 warning_with_id ("Octave:LaTeX:internal-error",
306 "latex_renderer:: failed to read png data. %s",
307 ee.message ().c_str ());
308
309 interpreter& interp
310 = __get_interpreter__ ("latex_renderer::read_image");
311
312 interp.recover_from_exception ();
313
314 return false;
315 }
316
317 data = uint8NDArray (dim_vector (4, width, height),
318 static_cast<uint8_t> (0));
319
320 for (int i = 0; i < height; i++)
321 {
322 for (int j = 0; j < width; j++)
323 {
324 data(0, j, i) = m_color(0);
325 data(1, j, i) = m_color(1);
326 data(2, j, i) = m_color(2);
327 data(3, j, i) = alpha(height-i-1, j);
328 }
329 }
330
331 return true;
332 }
333
334 void
335 latex_renderer::warn_helper (std::string caller, std::string txt,
336 std::string cmd, process_execution_result result)
337 {
338 if (m_testing && ! m_debug)
339 return;
340
341 if (! m_debug)
342 warning_with_id ("Octave:LaTeX:internal-error",
343 "latex_renderer: unable to compile \"%s\"",
344 txt.c_str ());
345 else
346 warning_with_id ("Octave:LaTeX:internal-error",
347 "latex_renderer: %s failed for string \"%s\"\n\
348* Command:\n\t%s\n\n* Error:\n%s\n\n* Stdout:\n%s",
349 caller.c_str (), txt.c_str (), cmd.c_str (),
350 result.err_msg ().c_str (),
351 result.stdout_output ().c_str ());
352 }
353
355 latex_renderer::render (const std::string& txt, int halign)
356 {
357 // Render if it was not already done
358 gh_manager& gh_mgr = octave::__get_gh_manager__ ("latex_renderer::render");
359
360 gh_manager::latex_data ldata = gh_mgr.get_latex_data (key (txt, halign));
361
362 if (! ldata.first.isempty ())
363 return ldata.first;
364
365 uint8NDArray data;
366
367 // First write the base .tex file
368 std::string base_file_name = write_tex_file (txt, halign);
369
370 if (base_file_name.empty ())
371 return data;
372
373 // Generate DVI file
374 std::string tex_file = quote_string (base_file_name + ".tex");
375 std::string dvi_file = quote_string (base_file_name + ".dvi");
376 std::string log_file = quote_string (base_file_name + ".log");
377
379 std::string cmd = (m_latex_binary + " -interaction=nonstopmode "
380 + "-output-directory=" + quote_string (m_tmp_dir) + " "
381 + tex_file);
382
383#if defined (OCTAVE_USE_WINDOWS_API)
384 cmd = quote_string (cmd);
385#endif
386
387 result = run_command_and_return_output (cmd);
388
389 if (result.exit_status () != 0)
390 {
391 warn_helper ("latex", txt, cmd, result);
392
393 if (txt != "?")
394 {
395 write_tex_file ("?", halign);
396
397 result = run_command_and_return_output (cmd);
398 if (result.exit_status () != 0)
399 return data;
400 }
401 else
402 return data;
403 }
404
405 double size_factor = m_fontsize / 10.0;
406
407
408 // Convert DVI to SVG, read file and store its content for later use in
409 // gl2ps_print
410 std::string svg_file = base_file_name + ".svg";
411
412 cmd = (m_dvisvg_binary + " -n "
413 + "-TS" + std::to_string (size_factor) + " "
414 + "-v1 -o " + quote_string (svg_file) + " "
415 + dvi_file);
416
417#if defined (OCTAVE_USE_WINDOWS_API)
418 cmd = quote_string (cmd);
419#endif
420
421 result = run_command_and_return_output (cmd);
422
423 if (result.exit_status () != 0)
424 {
425 warn_helper ("dvisvg", txt, cmd, result);
426 return data;
427 }
428
429 std::ifstream svg_stream (svg_file);
430 std::string svg_string;
431 svg_string.assign (std::istreambuf_iterator<char> (svg_stream),
432 std::istreambuf_iterator<char> ());
433
434 // Convert DVI to PNG, read file and format pixel data for later use in
435 // OpenGL
436 std::string png_file = base_file_name + ".png";
437
438 cmd = (m_dvipng_binary + " " + dvi_file + " "
439 + "-q -o " + quote_string (png_file) + " "
440 + "-bg Transparent -D "
441 + std::to_string (std::floor (72.0 * size_factor)));
442
443#if defined (OCTAVE_USE_WINDOWS_API)
444 cmd = quote_string (cmd);
445#endif
446
447 result = run_command_and_return_output (cmd);
448
449 if (result.exit_status () != 0)
450 {
451 warn_helper ("dvipng", txt, cmd, result);
452 return data;
453 }
454
455 if (! read_image (png_file, data))
456 return data;
457
458 // Cache pixel and svg data for this string
459 ldata.first = data;
460 ldata.second = svg_string;
461
462 gh_mgr.set_latex_data (key (txt, halign), ldata);
463
464 if (m_debug)
465 std::cout << "* Caching " << key (txt, halign) << std::endl;
466
467 return data;
468 }
469
470 void
471 latex_renderer::text_to_pixels (const std::string& txt, uint8NDArray& pixels,
472 Matrix& bbox, int halign, int valign,
473 double rotation,
474 const caseless_str& /*interpreter*/,
475 bool handle_rotation)
476 {
477 // Return early for empty strings
478 if (txt.empty ())
479 {
480 bbox = Matrix (1, 4, 0.0);
481 return;
482 }
483
484 if (ok ())
485 pixels = render (txt, halign);
486 else
487 pixels = uint8NDArray (dim_vector (4, 1, 1), static_cast<uint8_t> (0));
488
489 if (pixels.ndims () < 3 || pixels.isempty ())
490 return; // nothing to render
491
492 // Store unrotated bbox size
493 bbox = Matrix (1, 4, 0.0);
494 bbox (2) = pixels.dim2 ();
495 bbox (3) = pixels.dim3 ();
496
497 // Now rotate pixels if necessary
498 int rot_mode = rotation_to_mode (rotation);
499
500 if (! pixels.isempty ())
501 rotate_pixels (pixels, rot_mode);
502
503 // Move X0 and Y0 depending on alignments and eventually swap values
504 // for text rotated 90° 180° or 270°
505 fix_bbox_anchor (bbox, halign, valign, rot_mode, handle_rotation);
506 }
507
510 {
511 latex_renderer *renderer = new latex_renderer ();
512
513 return renderer;
514 }
515}
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)
octave_idx_type numel(void) const
Number of elements in the array.
Definition: Array.h:411
octave_idx_type dim2(void) const
Definition: Array.h:456
bool isempty(void) const
Size of the specified dimension.
Definition: Array.h:607
int ndims(void) const
Size of the specified dimension.
Definition: Array.h:627
octave_idx_type dim3(void) const
Size of the specified dimension.
Definition: Array.h:465
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
Vector representing the dimensions (size) of an Array.
Definition: dim-vector.h:94
void recover_from_exception(void)
int rotation_to_mode(double rotation) const
void rotate_pixels(uint8NDArray &pixels, int rot_mode) const
void fix_bbox_anchor(Matrix &bbox, int halign, int valign, int rot_mode, bool handle_rotation) const
Matrix get_extent(text_element *, double)
void warn_helper(std::string caller, std::string txt, std::string cmd, process_execution_result result)
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(const std::string &txt, double rotation, const caseless_str &interpreter)
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)
std::string key(const std::string &txt, int halign)
void set_color(const Matrix &c)
octave_map get_system_fonts(void)
void set_font(const std::string &, const std::string &, const std::string &, double size)
std::string write_tex_file(const std::string &txt, int halign)
uint8NDArray render(const std::string &txt, int halign=0)
std::string err_msg(void) const
Definition: oct-process.h:61
std::string stdout_output(void) const
Definition: oct-process.h:63
static std::string getenv(const std::string &name)
Definition: oct-env.cc:294
void set_svg_element(const std::string &svg)
void set_color(const uint8NDArray &c)
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
void warning_with_id(const char *id, const char *fmt,...)
Definition: error.cc:1070
class OCTAVE_API Matrix
Definition: mx-fwd.h:31
std::complex< T > floor(const std::complex< T > &x)
Definition: lo-mappers.h:130
std::string concat(const std::string &dir, const std::string &file)
Definition: file-ops.cc:349
std::string tempnam(const std::string &dir, const std::string &pfx)
Definition: file-ops.cc:641
std::ifstream ifstream(const std::string &filename, const std::ios::openmode mode)
Definition: lo-sysdep.cc:400
std::ofstream ofstream(const std::string &filename, const std::ios::openmode mode)
Definition: lo-sysdep.cc:414
int mkdir(const std::string &nm, mode_t md)
Definition: file-ops.cc:399
int recursive_rmdir(const std::string &name)
Definition: file-ops.cc:552
base_text_renderer * make_latex_text_renderer(void)
std::string quote_string(std::string str)
interpreter & __get_interpreter__(const std::string &who)
gh_manager & __get_gh_manager__(const std::string &who)
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