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