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