GNU Octave  6.2.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
ft-text-renderer.cc
Go to the documentation of this file.
1 ////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (C) 2009-2021 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 "base-text-renderer.h"
31 #include "ft-text-renderer.h"
32 
33 #if defined (HAVE_FREETYPE)
34 
35 #if defined (HAVE_PRAGMA_GCC_DIAGNOSTIC)
36 # pragma GCC diagnostic push
37 # pragma GCC diagnostic ignored "-Wold-style-cast"
38 #endif
39 
40 #include <ft2build.h>
41 #include FT_FREETYPE_H
42 #include FT_GLYPH_H
43 
44 #if defined (HAVE_FONTCONFIG)
45 # include <fontconfig/fontconfig.h>
46 #endif
47 
48 #if defined (HAVE_PRAGMA_GCC_DIAGNOSTIC)
49 # pragma GCC diagnostic pop
50 #endif
51 
52 #include <clocale>
53 #include <cwchar>
54 #include <map>
55 #include <utility>
56 
57 #include "singleton-cleanup.h"
58 #include "unistr-wrappers.h"
59 
60 #include "defaults.h"
61 #include "error.h"
62 #include "file-ops.h"
63 #include "oct-env.h"
64 #include "pr-output.h"
65 #include "text-renderer.h"
66 
67 namespace octave
68 {
69  // FIXME: maybe issue at most one warning per glyph/font/size/weight
70  // combination.
71 
72  static void
73  warn_missing_glyph (FT_ULong c)
74  {
75  warning_with_id ("Octave:missing-glyph",
76  "text_renderer: skipping missing glyph for character '%lx'", c);
77  }
78 
79  static void
80  warn_glyph_render (FT_ULong c)
81  {
82  warning_with_id ("Octave:glyph-render",
83  "text_renderer: unable to render glyph for character '%lx'", c);
84  }
85 
86 #if defined (_MSC_VER)
87  // FIXME: is this really needed?
88  //
89  // This is just a trick to avoid multiple symbol definitions.
90  // PermMatrix.h contains a dllexport'ed Array<octave_idx_type>
91  // that will cause MSVC not to generate a new instantiation and
92  // use the imported one instead.
93 # include "PermMatrix.h"
94 #endif
95 
96  // Forward declaration
97  static void ft_face_destroyed (void *object);
98 
99  class
100  ft_manager
101  {
102  private:
103 
104  ft_manager (void)
105  : library (), freetype_initialized (false), fontconfig_initialized (false)
106  {
107  if (FT_Init_FreeType (&library))
108  error ("unable to initialize FreeType library");
109  else
110  freetype_initialized = true;
111 
112 #if defined (HAVE_FONTCONFIG)
113  if (! FcInit ())
114  error ("unable to initialize fontconfig library");
115  else
116  fontconfig_initialized = true;
117 #endif
118  }
119 
120  public:
121 
122  // No copying!
123 
124  ft_manager (const ft_manager&) = delete;
125 
126  ft_manager& operator = (const ft_manager&) = delete;
127 
128  private:
129 
130  ~ft_manager (void)
131  {
132  if (freetype_initialized)
133  FT_Done_FreeType (library);
134 
135 #if defined (HAVE_FONTCONFIG)
136  // FIXME: Skip the call to FcFini because it can trigger the assertion
137  //
138  // octave: fccache.c:507: FcCacheFini: Assertion 'fcCacheChains[i] == ((void *)0)' failed.
139  //
140  // if (fontconfig_initialized)
141  // FcFini ();
142 #endif
143  }
144 
145  public:
146 
147  static bool instance_ok (void)
148  {
149  bool retval = true;
150 
151  if (! instance)
152  {
153  instance = new ft_manager ();
154  singleton_cleanup_list::add (cleanup_instance);
155  }
156 
157  return retval;
158  }
159 
160  static void cleanup_instance (void) { delete instance; instance = nullptr; }
161 
162  static FT_Face get_font (const std::string& name, const std::string& weight,
163  const std::string& angle, double size)
164  {
165  return (instance_ok ()
166  ? instance->do_get_font (name, weight, angle, size)
167  : nullptr);
168  }
169 
171  {
172  return (instance_ok ()
173  ? instance->do_get_system_fonts ()
174  : octave_map ());
175  }
176 
177  static void font_destroyed (FT_Face face)
178  {
179  if (instance_ok ())
180  instance->do_font_destroyed (face);
181  }
182 
183  private:
184 
186 
187  typedef std::pair<std::string, double> ft_key;
188  typedef std::map<ft_key, FT_Face> ft_cache;
189 
190  // Cache the fonts loaded by FreeType. This cache only contains
191  // weak references to the fonts, strong references are only present
192  // in class text_renderer.
194 
196  {
197  static octave_map font_map;
198 
199  if (font_map.isempty ())
200  {
201 #if defined (HAVE_FONTCONFIG)
202  FcConfig *config = FcConfigGetCurrent();
203  FcPattern *pat = FcPatternCreate ();
204  FcObjectSet *os = FcObjectSetBuild (FC_FAMILY, FC_SLANT, FC_WEIGHT,
205  FC_CHARSET, nullptr);
206  FcFontSet *fs = FcFontList (config, pat, os);
207 
208  if (fs->nfont > 0)
209  {
210  // Mark fonts that have at least all printable ASCII chars
211  FcCharSet *minimal_charset = FcCharSetCreate ();
212  for (int i = 32; i < 127; i++)
213  FcCharSetAddChar (minimal_charset, static_cast<FcChar32> (i));
214 
215  string_vector fields (4);
216  fields(0) = "family";
217  fields(1) = "angle";
218  fields(2) = "weight";
219  fields(3) = "suitable";
220 
221  dim_vector dv (1, fs->nfont);
222  Cell families (dv);
223  Cell angles (dv);
224  Cell weights (dv);
225  Cell suitable (dv);
226 
227  unsigned char *family;
228  int val;
229  for (int i = 0; fs && i < fs->nfont; i++)
230  {
231  FcPattern *font = fs->fonts[i];
232  if (FcPatternGetString (font, FC_FAMILY, 0, &family)
233  == FcResultMatch)
234  families(i) = std::string (reinterpret_cast<char*> (family));
235  else
236  families(i) = "unknown";
237 
238  if (FcPatternGetInteger (font, FC_SLANT, 0, &val)
239  == FcResultMatch)
240  angles(i) = (val == FC_SLANT_ITALIC
241  || val == FC_SLANT_OBLIQUE)
242  ? "italic" : "normal";
243  else
244  angles(i) = "unknown";
245 
246  if (FcPatternGetInteger (font, FC_WEIGHT, 0, &val)
247  == FcResultMatch)
248  weights(i) = (val == FC_WEIGHT_BOLD
249  || val == FC_WEIGHT_DEMIBOLD)
250  ? "bold" : "normal";
251  else
252  weights(i) = "unknown";
253 
254  FcCharSet *cset;
255  if (FcPatternGetCharSet (font, FC_CHARSET, 0, &cset)
256  == FcResultMatch)
257  suitable(i) = (FcCharSetIsSubset (minimal_charset, cset)
258  ? true : false);
259  else
260  suitable(i) = false;
261  }
262 
263  font_map = octave_map (dv, fields);
264 
265  font_map.assign ("family", families);
266  font_map.assign ("angle", angles);
267  font_map.assign ("weight", weights);
268  font_map.assign ("suitable", suitable);
269 
270  if (fs)
271  FcFontSetDestroy (fs);
272  }
273 #endif
274  }
275 
276  return font_map;
277  }
278 
279  FT_Face do_get_font (const std::string& name, const std::string& weight,
280  const std::string& angle, double size)
281  {
282  FT_Face retval = nullptr;
283 
284 #if defined (HAVE_FT_REFERENCE_FACE)
285  // Look first into the font cache, then use fontconfig. If the font
286  // is present in the cache, simply add a reference and return it.
287 
288  ft_key key (name + ':' + weight + ':' + angle, size);
289  ft_cache::const_iterator it = cache.find (key);
290 
291  if (it != cache.end ())
292  {
293  FT_Reference_Face (it->second);
294  return it->second;
295  }
296 #endif
297 
298  static std::string fonts_dir;
299 
300  if (fonts_dir.empty ())
301  {
302  fonts_dir = sys::env::getenv ("OCTAVE_FONTS_DIR");
303 
304  if (fonts_dir.empty ())
305 #if defined (SYSTEM_FREEFONT_DIR)
306  fonts_dir = SYSTEM_FREEFONT_DIR;
307 #else
308  fonts_dir = config::oct_fonts_dir ();
309 #endif
310  }
311 
312 
313  // Default font file
314  std::string file;
315 
316  if (! fonts_dir.empty ())
317  {
318  file = fonts_dir + sys::file_ops::dir_sep_str () + "FreeSans";
319 
320  if (weight == "bold")
321  file += "Bold";
322 
323  if (angle == "italic" || angle == "oblique")
324  file += "Oblique";
325 
326  file += ".otf";
327  }
328 
329 #if defined (HAVE_FONTCONFIG)
330  if (name != "*" && fontconfig_initialized)
331  {
332  int fc_weight, fc_angle;
333 
334  if (weight == "bold")
335  fc_weight = FC_WEIGHT_BOLD;
336  else
337  fc_weight = FC_WEIGHT_NORMAL;
338 
339  if (angle == "italic")
340  fc_angle = FC_SLANT_ITALIC;
341  else if (angle == "oblique")
342  fc_angle = FC_SLANT_OBLIQUE;
343  else
344  fc_angle = FC_SLANT_ROMAN;
345 
346  FcPattern *pat = FcPatternCreate ();
347 
348  FcPatternAddString (pat, FC_FAMILY,
349  (reinterpret_cast<const FcChar8 *>
350  (name.c_str ())));
351 
352  FcPatternAddInteger (pat, FC_WEIGHT, fc_weight);
353  FcPatternAddInteger (pat, FC_SLANT, fc_angle);
354  FcPatternAddDouble (pat, FC_PIXEL_SIZE, size);
355 
356  if (FcConfigSubstitute (nullptr, pat, FcMatchPattern))
357  {
358  FcResult res;
359  FcPattern *match;
360 
361  FcDefaultSubstitute (pat);
362  match = FcFontMatch (nullptr, pat, &res);
363 
364  // FIXME: originally, this test also required that
365  // res != FcResultNoMatch. Is that really needed?
366  if (match)
367  {
368  unsigned char *tmp;
369 
370  FcPatternGetString (match, FC_FILE, 0, &tmp);
371  file = reinterpret_cast<char *> (tmp);
372  }
373  else
374  ::warning ("could not match any font: %s-%s-%s-%g, using default font",
375  name.c_str (), weight.c_str (), angle.c_str (),
376  size);
377 
378  if (match)
379  FcPatternDestroy (match);
380  }
381 
382  FcPatternDestroy (pat);
383  }
384 #endif
385 
386  if (file.empty ())
387  ::warning ("unable to find default font files");
388  else
389  {
390  if (FT_New_Face (library, file.c_str (), 0, &retval))
391  ::warning ("ft_manager: unable to load font: %s", file.c_str ());
392 #if defined (HAVE_FT_REFERENCE_FACE)
393  else
394  {
395  // Install a finalizer to notify ft_manager that the font is
396  // being destroyed. The class ft_manager only keeps weak
397  // references to font objects.
398 
399  retval->generic.data = new ft_key (key);
400  retval->generic.finalizer = ft_face_destroyed;
401 
402  // Insert loaded font into the cache.
403  if (FT_Reference_Face (retval) == 0)
404  cache[key] = retval;
405  }
406 #endif
407  }
408 
409  return retval;
410  }
411 
412  void do_font_destroyed (FT_Face face)
413  {
414  if (face->generic.data)
415  {
416  ft_key *pkey = reinterpret_cast<ft_key *> (face->generic.data);
417 
418  cache.erase (*pkey);
419  delete pkey;
420  face->generic.data = nullptr;
421  FT_Done_Face (face);
422  }
423  }
424 
425  private:
426  FT_Library library;
429  };
430 
431  ft_manager *ft_manager::instance = nullptr;
432 
433  static void
434  ft_face_destroyed (void *object)
435  {
436  ft_manager::font_destroyed (reinterpret_cast<FT_Face> (object));
437  }
438 
439  class
440  OCTINTERP_API
442  {
443  public:
444 
445  enum
446  {
447  MODE_BBOX = 0,
448  MODE_RENDER = 1
449  };
450 
451  enum
452  {
453  ROTATION_0 = 0,
454  ROTATION_90 = 1,
455  ROTATION_180 = 2,
456  ROTATION_270 = 3
457  };
458 
459  public:
460 
462  : base_text_renderer (), font (), bbox (1, 4, 0.0), halign (0),
463  xoffset (0), line_yoffset (0), yoffset (0), mode (MODE_BBOX),
464  color (dim_vector (1, 3), 0), m_do_strlist (false), m_strlist (),
465  line_xoffset (0), m_ymin (0), m_ymax (0), m_deltax (0),
466  m_max_fontsize (0), m_antialias (true)
467  { }
468 
469  // No copying!
470 
472 
473  ft_text_renderer& operator = (const ft_text_renderer&) = delete;
474 
475  ~ft_text_renderer (void) = default;
476 
477  void visit (text_element_string& e);
478 
479  void visit (text_element_list& e);
480 
481  void visit (text_element_subscript& e);
482 
483  void visit (text_element_superscript& e);
484 
485  void visit (text_element_color& e);
486 
487  void visit (text_element_fontsize& e);
488 
489  void visit (text_element_fontname& e);
490 
491  void visit (text_element_fontstyle& e);
492 
493  void visit (text_element_symbol& e);
494 
495  void visit (text_element_combined& e);
496 
497  void reset (void);
498 
499  uint8NDArray get_pixels (void) const { return pixels; }
500 
501  Matrix get_boundingbox (void) const { return bbox; }
502 
503  uint8NDArray render (text_element *elt, Matrix& box,
504  int rotation = ROTATION_0);
505 
506  Matrix get_extent (text_element *elt, double rotation = 0.0);
507  Matrix get_extent (const std::string& txt, double rotation,
508  const caseless_str& interpreter);
509 
510  void set_anti_aliasing (bool val) { m_antialias = val; };
511 
512  void set_font (const std::string& name, const std::string& weight,
513  const std::string& angle, double size);
514 
515  octave_map get_system_fonts (void);
516 
517  void set_color (const Matrix& c);
518 
519  void set_mode (int m);
520 
521  void text_to_pixels (const std::string& txt,
522  uint8NDArray& pxls, Matrix& bbox,
523  int halign, int valign, double rotation,
524  const caseless_str& interpreter,
525  bool handle_rotation);
526 
527  private:
528 
529  int rotation_to_mode (double rotation) const;
530 
531  // Class to hold information about fonts and a strong
532  // reference to the font objects loaded by FreeType.
533 
535  {
536  public:
537 
538  ft_font (void)
539  : text_renderer::font (), face (nullptr) { }
540 
541  ft_font (const std::string& nm, const std::string& wt,
542  const std::string& ang, double sz, FT_Face f = nullptr)
543  : text_renderer::font (nm, wt, ang, sz), face (f)
544  { }
545 
546  ft_font (const ft_font& ft);
547 
548  ~ft_font (void)
549  {
550  if (face)
551  FT_Done_Face (face);
552  }
553 
554  ft_font& operator = (const ft_font& ft);
555 
556  bool is_valid (void) const { return get_face (); }
557 
558  FT_Face get_face (void) const;
559 
560  private:
561 
562  mutable FT_Face face;
563  };
564 
565  void push_new_line (void);
566 
567  void update_line_bbox (void);
568 
569  void compute_bbox (void);
570 
571  int compute_line_xoffset (const Matrix& lb) const;
572 
573  FT_UInt process_character (FT_ULong code, FT_UInt previous = 0);
574 
575  public:
576 
577  void text_to_strlist (const std::string& txt,
578  std::list<text_renderer::string>& lst, Matrix& bbox,
579  int halign, int valign, double rotation,
580  const caseless_str& interp);
581 
582  private:
583 
584  // The current font used by the renderer.
586 
587  // Used to stored the bounding box corresponding to the rendered text.
588  // The bounding box has the form [x, y, w, h] where x and y represent the
589  // coordinates of the bottom left corner relative to the anchor point of
590  // the text (== start of text on the baseline). Due to font descent or
591  // multiple lines, the value y is usually negative.
593 
594  // Used to stored the rendered text. It's a 3D matrix with size MxNx4
595  // where M and N are the width and height of the bounding box.
597 
598  // Used to store the bounding box of each line. This is used to layout
599  // multiline text properly.
600  std::list<Matrix> line_bbox;
601 
602  // The current horizontal alignment. This is used to align multi-line text.
603  int halign;
604 
605  // The X offset for the next glyph.
606  int xoffset;
607 
608  // The Y offset of the baseline for the current line.
610 
611  // The Y offset of the baseline for the next glyph. The offset is relative
612  // to line_yoffset. The total Y offset is computed with:
613  // line_yoffset + yoffset.
614  int yoffset;
615 
616  // The current mode of the rendering process (box computing or rendering).
617  int mode;
618 
619  // The base color of the rendered text.
621 
622  // A list of parsed strings to be used for printing.
624  std::list<text_renderer::string> m_strlist;
625 
626  // The X offset of the baseline for the current line.
628 
629  // Min and max y coordinates of all glyphs in a line.
630  FT_Pos m_ymin;
631  FT_Pos m_ymax;
632 
633  // Difference between the advance and the actual extent of the latest glyph
634  FT_Pos m_deltax;
635 
636  // Used for computing the distance between lines.
638 
639  // Anti-aliasing.
641 
642  };
643 
644  void
645  ft_text_renderer::set_font (const std::string& name,
646  const std::string& weight,
647  const std::string& angle, double size)
648  {
649  // FIXME: take "fontunits" into account
650  font = ft_font (name, weight, angle, size, nullptr);
651  }
652 
653  octave_map
655  {
657  }
658 
659  void
661  {
662  switch (mode)
663  {
664  case MODE_BBOX:
665  {
666  // Create a new bbox entry based on the current font.
667 
668  FT_Face face = font.get_face ();
669 
670  if (face)
671  {
672  Matrix bb (1, 5, 0.0);
673 
674  line_bbox.push_back (bb);
675 
676  xoffset = yoffset = 0;
677  m_ymin = m_ymax = m_deltax = 0;
678  }
679  }
680  break;
681 
682  case MODE_RENDER:
683  {
684  // Move to the next line bbox, adjust xoffset based on alignment
685  // and yoffset based on the old and new line bbox.
686 
687  Matrix old_bbox = line_bbox.front ();
688  line_bbox.pop_front ();
689  Matrix new_bbox = line_bbox.front ();
690 
692  line_yoffset -= (-old_bbox(1) + math::round (0.4 * m_max_fontsize)
693  + (new_bbox(3) + new_bbox(1)));
694  yoffset = 0;
695  m_ymin = m_ymax = m_deltax = 0;
696  }
697  break;
698  }
699  }
700 
701  int
703  {
704  if (! bbox.isempty ())
705  {
706  switch (halign)
707  {
708  case 0:
709  return 0;
710  case 1:
711  return (bbox(2) - lb(2)) / 2;
712  case 2:
713  return (bbox(2) - lb(2));
714  }
715  }
716 
717  return 0;
718  }
719 
720  void
722  {
723  // Stack the various line bbox together and compute the final
724  // bounding box for the entire text string.
725 
726  bbox = Matrix ();
727 
728  switch (line_bbox.size ())
729  {
730  case 0:
731  break;
732 
733  case 1:
734  bbox = line_bbox.front ().extract (0, 0, 0, 3);
735  break;
736 
737  default:
738  for (const auto& lbox : line_bbox)
739  {
740  if (bbox.isempty ())
741  bbox = lbox.extract (0, 0, 0, 3);
742  else
743  {
744  double delta = math::round (0.4 * m_max_fontsize) + lbox(3);
745  bbox(1) -= delta;
746  bbox(3) += delta;
747  bbox(2) = math::max (bbox(2), lbox(2));
748  }
749  }
750  break;
751  }
752  }
753 
754  void
756  {
757  // Called after a font change, when in MODE_BBOX mode, to update the
758  // current line bbox with the new font metrics. This also includes the
759  // current yoffset, that is the offset of the current glyph's baseline
760  // the line's baseline.
761 
762  if (mode == MODE_BBOX)
763  {
764  Matrix& bb = line_bbox.back ();
765  bb(1) = m_ymin;
766  // Add one pixel to the bbox height to avoid occasional text clipping.
767  // See bug #55328.
768  bb(3) = (m_ymax + 1) - m_ymin;
769  if (m_deltax > 0)
770  bb(2) += m_deltax;
771  }
772  }
773 
774  void
776  {
777  mode = m;
778 
779  switch (mode)
780  {
781  case MODE_BBOX:
782  xoffset = line_yoffset = yoffset = 0;
783  m_max_fontsize = 0.0;
784  bbox = Matrix (1, 4, 0.0);
785  line_bbox.clear ();
786  push_new_line ();
787  break;
788 
789  case MODE_RENDER:
790  if (bbox.numel () != 4)
791  {
792  ::error ("ft_text_renderer: invalid bounding box, cannot render");
793 
794  xoffset = line_yoffset = yoffset = 0;
795  pixels = uint8NDArray ();
796  }
797  else
798  {
799  dim_vector d (4, octave_idx_type (bbox(2)),
800  octave_idx_type (bbox(3)));
801  pixels = uint8NDArray (d, static_cast<uint8_t> (0));
803  line_yoffset = -bbox(1);
804  yoffset = 0;
805  }
806  break;
807 
808  default:
809  error ("ft_text_renderer: invalid mode '%d'", mode);
810  break;
811  }
812  }
813  bool is_opaque (const FT_GlyphSlot &glyph, const int x, const int y)
814  {
815  // Borrowed from https://stackoverflow.com/questions/14800827/
816  // indexing-pixels-in-a-monochrome-freetype-glyph-buffer
817  int pitch = std::abs (glyph->bitmap.pitch);
818  unsigned char *row = &glyph->bitmap.buffer[pitch * y];
819  char cvalue = row[x >> 3];
820 
821  return ((cvalue & (128 >> (x & 7))) != 0);
822  }
823 
824  FT_UInt
825  ft_text_renderer::process_character (FT_ULong code, FT_UInt previous)
826  {
827  FT_Face face = font.get_face ();
828  FT_UInt glyph_index = 0;
829 
830  if (face)
831  {
832  glyph_index = FT_Get_Char_Index (face, code);
833 
834  if (code != '\n' && code != '\t'
835  && (! glyph_index
836  || FT_Load_Glyph (face, glyph_index, FT_LOAD_DEFAULT)))
837  {
838  glyph_index = 0;
839  warn_missing_glyph (code);
840  }
841  else if ((code == '\n') || (code == '\t'))
842  {
843  glyph_index = FT_Get_Char_Index (face, ' ');
844  if (! glyph_index
845  || FT_Load_Glyph (face, glyph_index, FT_LOAD_DEFAULT))
846  {
847  glyph_index = 0;
848  warn_missing_glyph (' ');
849  }
850  else if (code == '\n')
851  push_new_line ();
852  else
853  {
854  // Advance to next multiple of 4 times the width of the "space"
855  // character.
856  int x_tab = 4 * (face->glyph->advance.x >> 6);
857  xoffset = (1 + std::floor (1. * xoffset / x_tab)) * x_tab;
858  }
859  }
860  else
861  {
862  switch (mode)
863  {
864  case MODE_RENDER:
865  if (FT_Render_Glyph (face->glyph, (m_antialias
866  ? FT_RENDER_MODE_NORMAL
867  : FT_RENDER_MODE_MONO)))
868  {
869  glyph_index = 0;
870  warn_glyph_render (code);
871  }
872  else
873  {
874  FT_Bitmap& bitmap = face->glyph->bitmap;
875  int x0, y0;
876 
877  if (previous)
878  {
879  FT_Vector delta;
880 
881  FT_Get_Kerning (face, previous, glyph_index,
882  FT_KERNING_DEFAULT, &delta);
883  xoffset += (delta.x >> 6);
884  }
885 
886  x0 = xoffset + face->glyph->bitmap_left;
887  y0 = line_yoffset + yoffset + (face->glyph->bitmap_top - 1);
888 
889  // 'w' seems to have a negative -1
890  // face->glyph->bitmap_left, this is so we don't index out
891  // of bound, and assumes we've allocated the right amount of
892  // horizontal space in the bbox.
893  if (x0 < 0)
894  x0 = 0;
895 
896  for (int r = 0; static_cast<unsigned int> (r) < bitmap.rows; r++)
897  for (int c = 0; static_cast<unsigned int> (c) < bitmap.width; c++)
898  {
899  unsigned char pix
900  = (m_antialias
901  ? bitmap.buffer[r*bitmap.width+c]
902  : (is_opaque (face->glyph, c, r) ? 255 : 0));
903 
904  if (x0+c < 0 || x0+c >= pixels.dim2 ()
905  || y0-r < 0 || y0-r >= pixels.dim3 ())
906  {
907  // ::warning ("ft_text_renderer: x %d, y %d",
908  // x0+c, y0-r);
909  }
910  else if (pixels(3, x0+c, y0-r).value () == 0)
911  {
912  pixels(0, x0+c, y0-r) = color(0);
913  pixels(1, x0+c, y0-r) = color(1);
914  pixels(2, x0+c, y0-r) = color(2);
915  pixels(3, x0+c, y0-r) = pix;
916  }
917  }
918 
919  xoffset += (face->glyph->advance.x >> 6);
920  }
921  break;
922 
923  case MODE_BBOX:
924  Matrix& bb = line_bbox.back ();
925 
926  // If we have a previous glyph, use kerning information. This
927  // usually means moving a bit backward before adding the next
928  // glyph. That is, "delta.x" is usually < 0.
929  if (previous)
930  {
931  FT_Vector delta;
932 
933  FT_Get_Kerning (face, previous, glyph_index,
934  FT_KERNING_DEFAULT, &delta);
935 
936  xoffset += (delta.x >> 6);
937  }
938 
939  // Extend current X offset box by the width of the current
940  // glyph. Then extend the line bounding box if necessary.
941 
942  xoffset += (face->glyph->advance.x >> 6);
943  bb(2) = math::max (bb(2), xoffset);
944 
945  // Store the actual bbox vertical coordinates of this character
946  FT_Glyph glyph;
947  if (FT_Get_Glyph (face->glyph, &glyph))
948  warn_glyph_render (code);
949  else
950  {
951  FT_BBox glyph_bbox;
952  FT_Glyph_Get_CBox (glyph, FT_GLYPH_BBOX_UNSCALED,
953  &glyph_bbox);
954  m_deltax = (glyph_bbox.xMax - face->glyph->advance.x) >> 6;
955  m_ymin = math::min ((glyph_bbox.yMin >> 6) + yoffset,
956  m_ymin);
957  m_ymax = math::max ((glyph_bbox.yMax >> 6) + yoffset,
958  m_ymax);
959  FT_Done_Glyph (glyph);
960  update_line_bbox ();
961  }
962  break;
963  }
964  }
965  }
966 
967  return glyph_index;
968  }
969 
970  void
971  ft_text_renderer::text_to_strlist (const std::string& txt,
972  std::list<text_renderer::string>& lst,
973  Matrix& box,
974  int ha, int va, double rot,
975  const caseless_str& interp)
976  {
977  uint8NDArray pxls;
978 
979  // First run text_to_pixels which will also build the string list
980 
981  m_strlist = std::list<text_renderer::string> ();
982 
983  unwind_protect frame;
984  frame.protect_var (m_do_strlist);
985  frame.protect_var (m_strlist);
986  m_do_strlist = true;
987 
988  text_to_pixels (txt, pxls, box, ha, va, rot, interp, false);
989 
990  lst = m_strlist;
991  }
992 
993  void
995  {
996  if (font.is_valid ())
997  {
999  FT_UInt glyph_index, previous = 0;
1000 
1001  std::string str = e.string_value ();
1002  const uint8_t *c = reinterpret_cast<const uint8_t *> (str.c_str ());
1003  uint32_t u32_c;
1004 
1005  size_t n = str.size ();
1006  size_t icurr = 0;
1007  size_t ibegin = 0;
1008 
1009  // Initialize a new string
1010  std::string fname = font.get_face ()->family_name;
1012  std::vector<double> xdata;
1013 
1014  while (n > 0)
1015  {
1016  // Retrieve the length and the u32 representation of the current
1017  // character
1018  int mblen = octave_u8_strmbtouc_wrapper (&u32_c, c + icurr);
1019  if (mblen < 1)
1020  {
1021  // This is not an UTF-8 character, use a replacement character
1022  mblen = 1;
1023  u32_c = 0xFFFD;
1024  }
1025 
1026  n -= mblen;
1027 
1028  if (m_do_strlist && mode == MODE_RENDER)
1029  {
1030  if (u32_c == 10)
1031  {
1032  // Finish previous string in m_strlist before processing
1033  // the newline character
1034  fs.set_y (line_yoffset + yoffset);
1035  fs.set_color (color);
1036 
1037  std::string s = str.substr (ibegin, icurr - ibegin);
1038  if (! s.empty ())
1039  {
1040  fs.set_string (s);
1041  fs.set_y (line_yoffset + yoffset);
1042  fs.set_xdata (xdata);
1043  fs.set_family (fname);
1044  m_strlist.push_back (fs);
1045  }
1046  }
1047  else
1048  xdata.push_back (xoffset);
1049  }
1050 
1051  glyph_index = process_character (u32_c, previous);
1052 
1053  if (u32_c == 10)
1054  {
1055  previous = 0;
1056 
1057  if (m_do_strlist && mode == MODE_RENDER)
1058  {
1059  // Start a new string in m_strlist
1060  ibegin = icurr+1;
1061  xdata.clear ();
1062  fs = text_renderer::string (str.substr (ibegin), font,
1064  }
1065  }
1066  else
1067  previous = glyph_index;
1068 
1069  icurr += mblen;
1070  }
1071 
1072  if (m_do_strlist && mode == MODE_RENDER && ! fs.get_string ().empty ())
1073  {
1074  fs.set_y (line_yoffset + yoffset);
1075  fs.set_color (color);
1076  fs.set_xdata (xdata);
1077  fs.set_family (fname);
1078  m_strlist.push_back (fs);
1079  }
1080  }
1081  }
1082 
1083  void
1085  {
1086  // Save and restore (after processing the list) the current font and color.
1087 
1088  ft_font saved_font (font);
1089  uint8NDArray saved_color (color);
1090 
1092 
1093  font = saved_font;
1094  color = saved_color;
1095  }
1096 
1097  void
1099  {
1100  ft_font saved_font (font);
1101  int saved_line_yoffset = line_yoffset;
1102  int saved_yoffset = yoffset;
1103 
1104  double sz = font.get_size ();
1105 
1106  // Reducing font size by 70% produces decent results.
1108  std::max (5.0, sz * 0.7));
1109 
1110  if (font.is_valid ())
1111  {
1112  // Shifting the baseline by 15% of the font size gives decent results.
1113  yoffset -= std::ceil (sz * 0.15);
1114 
1115  if (mode == MODE_BBOX)
1116  update_line_bbox ();
1117  }
1118 
1120 
1121  font = saved_font;
1122  // If line_yoffset changed, this means we moved to a new line; hence yoffset
1123  // cannot be restored, because the saved value is not relevant anymore.
1124  if (line_yoffset == saved_line_yoffset)
1125  yoffset = saved_yoffset;
1126  }
1127 
1128  void
1130  {
1131  ft_font saved_font (font);
1132  int saved_line_yoffset = line_yoffset;
1133  int saved_yoffset = yoffset;
1134 
1135  double sz = font.get_size ();
1136 
1137  // Reducing font size by 70% produces decent results.
1139  std::max (5.0, sz * 0.7));
1140 
1141  if (saved_font.is_valid ())
1142  {
1143  // Shifting the baseline by 40% of the font size gives decent results.
1144  yoffset += std::ceil (sz * 0.4);
1145 
1146  if (mode == MODE_BBOX)
1147  update_line_bbox ();
1148  }
1149 
1151 
1152  font = saved_font;
1153  // If line_yoffset changed, this means we moved to a new line; hence yoffset
1154  // cannot be restored, because the saved value is not relevant anymore.
1155  if (line_yoffset == saved_line_yoffset)
1156  yoffset = saved_yoffset;
1157  }
1158 
1159  void
1161  {
1162  if (mode == MODE_RENDER)
1163  set_color (e.get_color ());
1164  }
1165 
1166  void
1168  {
1169  double sz = e.get_fontsize ();
1170 
1171  // FIXME: Matlab documentation says that the font size is expressed
1172  // in the text object FontUnit.
1173 
1174  set_font (font.get_name (), font.get_weight (), font.get_angle (), sz);
1175 
1176  if (mode == MODE_BBOX)
1177  update_line_bbox ();
1178  }
1179 
1180  void
1182  {
1184  font.get_size ());
1185 
1186  if (mode == MODE_BBOX)
1187  update_line_bbox ();
1188  }
1189 
1190  void
1192  {
1193  switch (e.get_fontstyle ())
1194  {
1196  set_font (font.get_name (), "normal", "normal", font.get_size ());
1197  break;
1198 
1200  set_font (font.get_name (), "bold", "normal", font.get_size ());
1201  break;
1202 
1204  set_font (font.get_name (), "normal", "italic", font.get_size ());
1205  break;
1206 
1208  set_font (font.get_name (), "normal", "oblique", font.get_size ());
1209  break;
1210  }
1211 
1212  if (mode == MODE_BBOX)
1213  update_line_bbox ();
1214  }
1215 
1216  void
1218  {
1219  uint32_t code = e.get_symbol_code ();
1220 
1221  std::vector<double> xdata (1, xoffset);
1223 
1225  {
1226  process_character (code);
1227  if (m_do_strlist && mode == MODE_RENDER)
1228  {
1229  fs.set_code (code);
1230  fs.set_xdata (xdata);
1231  }
1232  }
1233  else if (font.is_valid ())
1234  ::warning ("ignoring unknown symbol: %d", e.get_symbol ());
1235 
1236  if (m_do_strlist && mode == MODE_RENDER && fs.get_code ())
1237  {
1238  fs.set_y (line_yoffset + yoffset);
1239  fs.set_color (color);
1240  fs.set_family (font.get_face ()->family_name);
1241  m_strlist.push_back (fs);
1242  }
1243  }
1244 
1245  void
1247  {
1248  int saved_xoffset = xoffset;
1249  int max_xoffset = xoffset;
1250 
1251  for (auto *txt_elt : e)
1252  {
1253  xoffset = saved_xoffset;
1254  txt_elt->accept (*this);
1255  max_xoffset = math::max (xoffset, max_xoffset);
1256  }
1257 
1258  xoffset = max_xoffset;
1259  }
1260 
1261  void
1263  {
1264  set_mode (MODE_BBOX);
1265  set_color (Matrix (1, 3, 0.0));
1266  m_strlist = std::list<text_renderer::string> ();
1267  }
1268 
1269  void
1271  {
1272  if (c.numel () == 3)
1273  {
1274  color(0) = static_cast<uint8_t> (c(0)*255);
1275  color(1) = static_cast<uint8_t> (c(1)*255);
1276  color(2) = static_cast<uint8_t> (c(2)*255);
1277  }
1278  else
1279  ::warning ("ft_text_renderer::set_color: invalid color");
1280  }
1281 
1282  uint8NDArray
1284  {
1285  set_mode (MODE_BBOX);
1286  elt->accept (*this);
1287  compute_bbox ();
1288  box = bbox;
1289 
1291 
1292  if (pixels.numel () > 0)
1293  {
1294  elt->accept (*this);
1295 
1296  switch (rotation)
1297  {
1298  case ROTATION_0:
1299  break;
1300 
1301  case ROTATION_90:
1302  {
1303  Array<octave_idx_type> perm (dim_vector (3, 1));
1304  perm(0) = 0;
1305  perm(1) = 2;
1306  perm(2) = 1;
1307  pixels = pixels.permute (perm);
1308 
1309  Array<idx_vector> idx (dim_vector (3, 1));
1310  idx(0) = idx_vector (':');
1311  idx(1) = idx_vector (pixels.dim2 ()-1, -1, -1);
1312  idx(2) = idx_vector (':');
1313  pixels = uint8NDArray (pixels.index (idx));
1314  }
1315  break;
1316 
1317  case ROTATION_180:
1318  {
1319  Array<idx_vector> idx (dim_vector (3, 1));
1320  idx(0) = idx_vector (':');
1321  idx(1) = idx_vector (pixels.dim2 ()-1, -1, -1);
1322  idx(2) = idx_vector (pixels.dim3 ()-1, -1, -1);
1323  pixels = uint8NDArray (pixels.index (idx));
1324  }
1325  break;
1326 
1327  case ROTATION_270:
1328  {
1329  Array<octave_idx_type> perm (dim_vector (3, 1));
1330  perm(0) = 0;
1331  perm(1) = 2;
1332  perm(2) = 1;
1333  pixels = pixels.permute (perm);
1334 
1335  Array<idx_vector> idx (dim_vector (3, 1));
1336  idx(0) = idx_vector (':');
1337  idx(1) = idx_vector (':');
1338  idx(2) = idx_vector (pixels.dim3 ()-1, -1, -1);
1339  pixels = uint8NDArray (pixels.index (idx));
1340  }
1341  break;
1342  }
1343  }
1344 
1345  return pixels;
1346  }
1347 
1348  // Note:
1349  // x-extent accurately measures width of glyphs.
1350  // y-extent is overly large because it is measured from baseline-to-baseline.
1351  // Calling routines, such as ylabel, may need to account for this mismatch.
1352 
1353  Matrix
1355  {
1356  set_mode (MODE_BBOX);
1357  elt->accept (*this);
1358  compute_bbox ();
1359 
1360  Matrix extent (1, 2, 0.0);
1361 
1362  switch (rotation_to_mode (rotation))
1363  {
1364  case ROTATION_0:
1365  case ROTATION_180:
1366  extent(0) = bbox(2);
1367  extent(1) = bbox(3);
1368  break;
1369 
1370  case ROTATION_90:
1371  case ROTATION_270:
1372  extent(0) = bbox(3);
1373  extent(1) = bbox(2);
1374  }
1375 
1376  return extent;
1377  }
1378 
1379  Matrix
1380  ft_text_renderer::get_extent (const std::string& txt, double rotation,
1381  const caseless_str& interpreter)
1382  {
1384  Matrix extent = get_extent (elt, rotation);
1385  delete elt;
1386 
1387  return extent;
1388  }
1389 
1390  int
1391  ft_text_renderer::rotation_to_mode (double rotation) const
1392  {
1393  // Clip rotation to range [0, 360]
1394  while (rotation < 0)
1395  rotation += 360.0;
1396  while (rotation > 360.0)
1397  rotation -= 360.0;
1398 
1399  if (rotation == 0.0)
1400  return ROTATION_0;
1401  else if (rotation == 90.0)
1402  return ROTATION_90;
1403  else if (rotation == 180.0)
1404  return ROTATION_180;
1405  else if (rotation == 270.0)
1406  return ROTATION_270;
1407  else
1408  return ROTATION_0;
1409  }
1410 
1411  void
1412  ft_text_renderer::text_to_pixels (const std::string& txt,
1413  uint8NDArray& pxls, Matrix& box,
1414  int _halign, int valign, double rotation,
1415  const caseless_str& interpreter,
1416  bool handle_rotation)
1417  {
1418  int rot_mode = rotation_to_mode (rotation);
1419 
1420  halign = _halign;
1421 
1423  pxls = render (elt, box, rot_mode);
1424  delete elt;
1425 
1426  if (pxls.isempty ())
1427  return; // nothing to render
1428 
1429  switch (halign)
1430  {
1431  case 1:
1432  box(0) = -box(2)/2;
1433  break;
1434 
1435  case 2:
1436  box(0) = -box(2);
1437  break;
1438 
1439  default:
1440  box(0) = 0;
1441  break;
1442  }
1443 
1444  switch (valign)
1445  {
1446  case 1:
1447  box(1) = -box(3)/2;
1448  break;
1449 
1450  case 2:
1451  box(1) = -box(3);
1452  break;
1453 
1454  case 3:
1455  break;
1456 
1457  case 4:
1458  box(1) = -box(3)-box(1);
1459  break;
1460 
1461  default:
1462  box(1) = 0;
1463  break;
1464  }
1465 
1466  if (handle_rotation)
1467  {
1468  switch (rot_mode)
1469  {
1470  case ROTATION_90:
1471  std::swap (box(0), box(1));
1472  std::swap (box(2), box(3));
1473  box(0) = -box(0)-box(2);
1474  break;
1475 
1476  case ROTATION_180:
1477  box(0) = -box(0)-box(2);
1478  box(1) = -box(1)-box(3);
1479  break;
1480 
1481  case ROTATION_270:
1482  std::swap (box(0), box(1));
1483  std::swap (box(2), box(3));
1484  box(1) = -box(1)-box(3);
1485  break;
1486  }
1487  }
1488  }
1489 
1491  : text_renderer::font (ft), face (nullptr)
1492  {
1493 #if defined (HAVE_FT_REFERENCE_FACE)
1494  FT_Face ft_face = ft.get_face ();
1495 
1496  if (ft_face && FT_Reference_Face (ft_face) == 0)
1497  face = ft_face;
1498 #endif
1499  }
1500 
1503  {
1504  if (&ft != this)
1505  {
1507 
1508  if (face)
1509  {
1510  FT_Done_Face (face);
1511  face = nullptr;
1512  }
1513 
1514 #if defined (HAVE_FT_REFERENCE_FACE)
1515  FT_Face ft_face = ft.get_face ();
1516 
1517  if (ft_face && FT_Reference_Face (ft_face) == 0)
1518  face = ft_face;
1519 #endif
1520  }
1521 
1522  return *this;
1523  }
1524 
1525  FT_Face
1527  {
1528  if (! face && ! name.empty ())
1529  {
1530  face = ft_manager::get_font (name, weight, angle, size);
1531 
1532  if (face)
1533  {
1534  if (FT_Set_Char_Size (face, 0, size*64, 0, 0))
1535  ::warning ("ft_text_renderer: unable to set font size to %g", size);
1536  }
1537  else
1538  ::warning ("ft_text_renderer: unable to load appropriate font");
1539  }
1540 
1541  return face;
1542  }
1543 }
1544 
1545 #endif
1546 
1547 namespace octave
1548 {
1551  {
1552 #if defined (HAVE_FREETYPE)
1553  return new ft_text_renderer ();
1554 #else
1555  return 0;
1556 #endif
1557  }
1558 }
charNDArray max(char d, const charNDArray &m)
Definition: chNDArray.cc:230
octave_idx_type dim3(void) const
Size of the specified dimension.
Definition: Array.h:431
octave_idx_type numel(void) const
Number of elements in the array.
Definition: Array.h:377
const T * data(void) const
Size of the specified dimension.
Definition: Array.h:581
Array< T > index(const idx_vector &i) const
Indexing without resizing.
Definition: Array.cc:698
octave_idx_type dim2(void) const
Definition: Array.h:422
bool isempty(void) const
Size of the specified dimension.
Definition: Array.h:572
Definition: Cell.h:43
MArray< T > permute(const Array< octave_idx_type > &vec, bool inv=false) const
Definition: MArray.h:96
Definition: dMatrix.h:42
Matrix extract(octave_idx_type r1, octave_idx_type c1, octave_idx_type r2, octave_idx_type c2) const
Definition: dMatrix.cc:397
Vector representing the dimensions (size) of an Array.
Definition: dim-vector.h:95
void do_font_destroyed(FT_Face face)
static FT_Face get_font(const std::string &name, const std::string &weight, const std::string &angle, double size)
std::pair< std::string, double > ft_key
static ft_manager * instance
static octave_map get_system_fonts(void)
static bool instance_ok(void)
FT_Face do_get_font(const std::string &name, const std::string &weight, const std::string &angle, double size)
static void cleanup_instance(void)
static octave_map do_get_system_fonts(void)
static void font_destroyed(FT_Face face)
std::map< ft_key, FT_Face > ft_cache
ft_manager(const ft_manager &)=delete
ft_font & operator=(const ft_font &ft)
ft_font(const std::string &nm, const std::string &wt, const std::string &ang, double sz, FT_Face f=nullptr)
std::list< text_renderer::string > m_strlist
ft_text_renderer(const ft_text_renderer &)=delete
std::list< Matrix > line_bbox
void visit(text_element_string &e)
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 *elt, double rotation=0.0)
int compute_line_xoffset(const Matrix &lb) const
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_boundingbox(void) const
void set_font(const std::string &name, const std::string &weight, const std::string &angle, double size)
uint8NDArray get_pixels(void) const
void set_anti_aliasing(bool val)
octave_map get_system_fonts(void)
uint8NDArray render(text_element *elt, Matrix &box, int rotation=ROTATION_0)
void set_color(const Matrix &c)
int rotation_to_mode(double rotation) const
FT_UInt process_character(FT_ULong code, FT_UInt previous=0)
~ft_text_renderer(void)=default
static std::string getenv(const std::string &name)
Definition: oct-env.cc:271
const std::string & get_fontname(void) const
Definition: text-engine.h:247
double get_fontsize(void) const
Definition: text-engine.h:268
fontstyle get_fontstyle(void) const
Definition: text-engine.h:226
std::string string_value(void) const
Definition: text-engine.h:79
uint32_t get_symbol_code(void) const
Definition: text-engine.cc:36
int get_symbol(void) const
Definition: text-engine.h:103
virtual void accept(text_processor &p)=0
virtual text_element * parse(const std::string &s)=0
virtual void visit(text_element_string &e)=0
std::string get_angle(void) const
double get_size(void) const
std::string get_weight(void) const
font & operator=(const font &ft)
std::string get_name(void) const
void set_family(const std::string &nm)
void set_string(const std::string &s)
void set_code(const uint32_t c)
void set_xdata(const std::vector< double > &x0)
void set_y(const double y0)
std::string get_string(void) const
uint32_t get_code(void) const
void set_color(const uint8NDArray &c)
void assign(const std::string &k, const Cell &val)
Definition: oct-map.h:365
bool isempty(void) const
Definition: oct-map.h:391
static void add(fptr f)
void warning(const char *fmt,...)
Definition: error.cc:1050
void warning_with_id(const char *id, const char *fmt,...)
Definition: error.cc:1065
void error(const char *fmt,...)
Definition: error.cc:968
QString name
F77_RET_T const F77_DBLE const F77_DBLE F77_DBLE * d
F77_RET_T const F77_DBLE * x
T octave_idx_type m
Definition: mx-inlines.cc:773
octave_idx_type n
Definition: mx-inlines.cc:753
T * r
Definition: mx-inlines.cc:773
std::string oct_fonts_dir(void)
Definition: defaults.cc:347
T max(T x, T y)
Definition: lo-mappers.h:361
double round(double x)
Definition: lo-mappers.h:136
std::complex< T > ceil(const std::complex< T > &x)
Definition: lo-mappers.h:103
std::complex< T > floor(const std::complex< T > &x)
Definition: lo-mappers.h:130
T min(T x, T y)
Definition: lo-mappers.h:354
std::string dir_sep_str(void)
Definition: file-ops.cc:243
static double f(double k, double l_nu, double c_pm)
Definition: randpoisson.cc:118
static void ft_face_destroyed(void *object)
static void warn_glyph_render(FT_ULong c)
static void warn_missing_glyph(FT_ULong c)
base_text_renderer * make_ft_text_renderer(void)
bool is_opaque(const FT_GlyphSlot &glyph, const int x, const int y)
static octave_value box(JNIEnv *jni_env, void *jobj, void *jcls_arg=nullptr)
octave_value::octave_value(const Array< char > &chm, char type) return retval
Definition: ov.cc:811
static T abs(T x)
Definition: pr-output.cc:1678
intNDArray< octave_uint8 > uint8NDArray
Definition: uint8NDArray.h:36
int octave_u8_strmbtouc_wrapper(uint32_t *puc, const uint8_t *src)