GNU Octave  4.4.1
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) 2016-2018 John W. Eaton
4 Copyright (C) 2009-2018 Michael Goffioul
5 
6 This file is part of Octave.
7 
8 Octave is free software: you can redistribute it and/or modify it
9 under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12 
13 Octave is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17 
18 You should have received a copy of the GNU General Public License
19 along with Octave; see the file COPYING. If not, see
20 <https://www.gnu.org/licenses/>.
21 
22 */
23 
24 #if defined (HAVE_CONFIG_H)
25 # include "config.h"
26 #endif
27 
28 #include "base-text-renderer.h"
29 #include "ft-text-renderer.h"
30 
31 #if defined (HAVE_FREETYPE)
32 
33 #if defined (HAVE_PRAGMA_GCC_DIAGNOSTIC)
34 # pragma GCC diagnostic push
35 # pragma GCC diagnostic ignored "-Wold-style-cast"
36 #endif
37 
38 #include <ft2build.h>
39 #include FT_FREETYPE_H
40 
41 #if defined (HAVE_FONTCONFIG)
42 # include <fontconfig/fontconfig.h>
43 #endif
44 
45 #if defined (HAVE_PRAGMA_GCC_DIAGNOSTIC)
46 # pragma GCC diagnostic pop
47 #endif
48 
49 #include <clocale>
50 #include <cwchar>
51 #include <iostream>
52 #include <map>
53 #include <utility>
54 
55 #include "singleton-cleanup.h"
56 
57 #include "defaults.h"
58 #include "error.h"
59 #include "file-ops.h"
60 #include "oct-env.h"
61 #include "pr-output.h"
62 #include "text-renderer.h"
63 
64 namespace octave
65 {
66  // FIXME: maybe issue at most one warning per glyph/font/size/weight
67  // combination.
68 
69  static void
70  warn_missing_glyph (FT_ULong c)
71  {
72  warning_with_id ("Octave:missing-glyph",
73  "text_renderer: skipping missing glyph for character '%x'", c);
74  }
75 
76  static void
77  warn_glyph_render (FT_ULong c)
78  {
79  warning_with_id ("Octave:glyph-render",
80  "text_renderer: unable to render glyph for character '%x'", c);
81  }
82 
83 #if defined (_MSC_VER)
84  // FIXME: is this really needed?
85  //
86  // This is just a trick to avoid multiple symbol definitions.
87  // PermMatrix.h contains a dllexport'ed Array<octave_idx_type>
88  // that will cause MSVC not to generate a new instantiation and
89  // use the imported one instead.
90 # include "PermMatrix.h"
91 #endif
92 
93  // Forward declaration
94  static void ft_face_destroyed (void *object);
95 
96  class
98  {
99  private:
100 
101  ft_manager (void)
102  : library (), freetype_initialized (false), fontconfig_initialized (false)
103  {
104  if (FT_Init_FreeType (&library))
105  error ("unable to initialize FreeType library");
106  else
107  freetype_initialized = true;
108 
109 #if defined (HAVE_FONTCONFIG)
110  if (! FcInit ())
111  error ("unable to initialize fontconfig library");
112  else
113  fontconfig_initialized = true;
114 #endif
115  }
116 
117  public:
118 
119  // No copying!
120 
121  ft_manager (const ft_manager&) = delete;
122 
123  ft_manager& operator = (const ft_manager&) = delete;
124 
125  private:
126 
127  ~ft_manager (void)
128  {
129  if (freetype_initialized)
130  FT_Done_FreeType (library);
131 
132 #if defined (HAVE_FONTCONFIG)
133  // FIXME: Skip the call to FcFini because it can trigger the assertion
134  //
135  // octave: fccache.c:507: FcCacheFini: Assertion 'fcCacheChains[i] == ((void *)0)' failed.
136  //
137  // if (fontconfig_initialized)
138  // FcFini ();
139 #endif
140  }
141 
142  public:
143 
144  static bool instance_ok (void)
145  {
146  bool retval = true;
147 
148  if (! instance)
149  {
150  instance = new ft_manager ();
151 
152  if (instance)
153  singleton_cleanup_list::add (cleanup_instance);
154  }
155 
156  if (! instance)
157  error ("unable to create ft_manager!");
158 
159  return retval;
160  }
161 
162  static void cleanup_instance (void) { delete instance; instance = nullptr; }
163 
164  static FT_Face get_font (const std::string& name, const std::string& weight,
165  const std::string& angle, double size)
166  {
167  return (instance_ok ()
168  ? instance->do_get_font (name, weight, angle, size)
169  : nullptr);
170  }
171 
172  static void font_destroyed (FT_Face face)
173  {
174  if (instance_ok ())
175  instance->do_font_destroyed (face);
176  }
177 
178  private:
179 
181 
182  typedef std::pair<std::string, double> ft_key;
183  typedef std::map<ft_key, FT_Face> ft_cache;
184 
185  // Cache the fonts loaded by FreeType. This cache only contains
186  // weak references to the fonts, strong references are only present
187  // in class text_renderer.
189 
190  FT_Face do_get_font (const std::string& name, const std::string& weight,
191  const std::string& angle, double size)
192  {
193  FT_Face retval = nullptr;
194 
195 #if defined (HAVE_FT_REFERENCE_FACE)
196  // Look first into the font cache, then use fontconfig. If the font
197  // is present in the cache, simply add a reference and return it.
198 
199  ft_key key (name + ':' + weight + ':' + angle, size);
200  ft_cache::const_iterator it = cache.find (key);
201 
202  if (it != cache.end ())
203  {
204  FT_Reference_Face (it->second);
205  return it->second;
206  }
207 #endif
208 
209  static std::string fonts_dir;
210 
211  if (fonts_dir.empty ())
212  {
213  fonts_dir = sys::env::getenv ("OCTAVE_FONTS_DIR");
214 
215  if (fonts_dir.empty ())
216 #if defined (SYSTEM_FREEFONT_DIR)
217  fonts_dir = SYSTEM_FREEFONT_DIR;
218 #else
219  fonts_dir = config::oct_fonts_dir ();
220 #endif
221  }
222 
223 
224  // Default font file
226 
227  if (! fonts_dir.empty ())
228  {
229  file = fonts_dir + octave::sys::file_ops::dir_sep_str () + "FreeSans";
230 
231  if (weight == "bold")
232  file += "Bold";
233 
234  if (angle == "italic" || angle == "oblique")
235  file += "Oblique";
236 
237  file += ".otf";
238  }
239 
240 #if defined (HAVE_FONTCONFIG)
241  if (name != "*" && fontconfig_initialized)
242  {
243  int fc_weight, fc_angle;
244 
245  if (weight == "bold")
246  fc_weight = FC_WEIGHT_BOLD;
247  else if (weight == "light")
248  fc_weight = FC_WEIGHT_LIGHT;
249  else if (weight == "demi")
250  fc_weight = FC_WEIGHT_DEMIBOLD;
251  else
252  fc_weight = FC_WEIGHT_NORMAL;
253 
254  if (angle == "italic")
255  fc_angle = FC_SLANT_ITALIC;
256  else if (angle == "oblique")
257  fc_angle = FC_SLANT_OBLIQUE;
258  else
259  fc_angle = FC_SLANT_ROMAN;
260 
261  FcPattern *pat = FcPatternCreate ();
262 
263  FcPatternAddString (pat, FC_FAMILY,
264  (reinterpret_cast<const FcChar8 *>
265  (name.c_str ())));
266 
267  FcPatternAddInteger (pat, FC_WEIGHT, fc_weight);
268  FcPatternAddInteger (pat, FC_SLANT, fc_angle);
269  FcPatternAddDouble (pat, FC_PIXEL_SIZE, size);
270 
271  if (FcConfigSubstitute (nullptr, pat, FcMatchPattern))
272  {
273  FcResult res;
274  FcPattern *match;
275 
276  FcDefaultSubstitute (pat);
277  match = FcFontMatch (nullptr, pat, &res);
278 
279  // FIXME: originally, this test also required that
280  // res != FcResultNoMatch. Is that really needed?
281  if (match)
282  {
283  unsigned char *tmp;
284 
285  FcPatternGetString (match, FC_FILE, 0, &tmp);
286  file = reinterpret_cast<char *> (tmp);
287  }
288  else
289  ::warning ("could not match any font: %s-%s-%s-%g, using default font",
290  name.c_str (), weight.c_str (), angle.c_str (),
291  size);
292 
293  if (match)
294  FcPatternDestroy (match);
295  }
296 
297  FcPatternDestroy (pat);
298  }
299 #endif
300 
301  if (file.empty ())
302  ::warning ("unable to find default font files");
303  else
304  {
305  if (FT_New_Face (library, file.c_str (), 0, &retval))
306  ::warning ("ft_manager: unable to load font: %s", file.c_str ());
307 #if defined (HAVE_FT_REFERENCE_FACE)
308  else
309  {
310  // Install a finalizer to notify ft_manager that the font is
311  // being destroyed. The class ft_manager only keeps weak
312  // references to font objects.
313 
314  retval->generic.data = new ft_key (key);
315  retval->generic.finalizer = ft_face_destroyed;
316 
317  // Insert loaded font into the cache.
318 
319  cache[key] = retval;
320  }
321 #endif
322  }
323 
324  return retval;
325  }
326 
327  void do_font_destroyed (FT_Face face)
328  {
329  if (face->generic.data)
330  {
331  ft_key *pkey = reinterpret_cast<ft_key *> (face->generic.data);
332 
333  cache.erase (*pkey);
334  delete pkey;
335  face->generic.data = nullptr;
336  }
337  }
338 
339  private:
340  FT_Library library;
343  };
344 
345  ft_manager *ft_manager::instance = nullptr;
346 
347  static void
348  ft_face_destroyed (void *object)
349  {
350  octave::ft_manager::font_destroyed (reinterpret_cast<FT_Face> (object));
351  }
352 
353  class
354  OCTINTERP_API
356  {
357  public:
358 
359  enum
360  {
361  MODE_BBOX = 0,
362  MODE_RENDER = 1
363  };
364 
365  enum
366  {
367  ROTATION_0 = 0,
368  ROTATION_90 = 1,
369  ROTATION_180 = 2,
370  ROTATION_270 = 3
371  };
372 
373  public:
374 
376  : base_text_renderer (), font (), bbox (1, 4, 0.0), halign (0),
377  xoffset (0), line_yoffset (0), yoffset (0), mode (MODE_BBOX),
378  color (dim_vector (1, 3), 0)
379  { }
380 
381  // No copying!
382 
383  ft_text_renderer (const ft_text_renderer&) = delete;
384 
385  ft_text_renderer& operator = (const ft_text_renderer&) = delete;
386 
387  ~ft_text_renderer (void) = default;
388 
389  void visit (text_element_string& e);
390 
391  void visit (text_element_list& e);
392 
393  void visit (text_element_subscript& e);
394 
395  void visit (text_element_superscript& e);
396 
397  void visit (text_element_color& e);
398 
399  void visit (text_element_fontsize& e);
400 
401  void visit (text_element_fontname& e);
402 
403  void visit (text_element_fontstyle& e);
404 
405  void visit (text_element_symbol& e);
406 
407  void visit (text_element_combined& e);
408 
409  void reset (void);
410 
411  uint8NDArray get_pixels (void) const { return pixels; }
412 
413  Matrix get_boundingbox (void) const { return bbox; }
414 
415  uint8NDArray render (text_element *elt, Matrix& box,
416  int rotation = ROTATION_0);
417 
418  Matrix get_extent (text_element *elt, double rotation = 0.0);
419  Matrix get_extent (const std::string& txt, double rotation,
420  const caseless_str& interpreter);
421 
422  void set_font (const std::string& name, const std::string& weight,
423  const std::string& angle, double size);
424 
425  void set_color (const Matrix& c);
426 
427  void set_mode (int m);
428 
429  void text_to_pixels (const std::string& txt,
430  uint8NDArray& pxls, Matrix& bbox,
431  int halign, int valign, double rotation,
432  const caseless_str& interpreter,
433  bool handle_rotation);
434 
435  private:
436 
437  int rotation_to_mode (double rotation) const;
438 
439  // Class to hold information about fonts and a strong
440  // reference to the font objects loaded by FreeType.
441 
443  {
444  public:
445 
446  ft_font (void)
447  : text_renderer::font (), face (nullptr) { }
448 
449  ft_font (const std::string& nm, const std::string& wt,
450  const std::string& ang, double sz, FT_Face f = nullptr)
451  : text_renderer::font (nm, wt, ang, sz), face (f)
452  { }
453 
454  ft_font (const ft_font& ft);
455 
456  ~ft_font (void)
457  {
458  if (face)
459  FT_Done_Face (face);
460  }
461 
462  ft_font& operator = (const ft_font& ft);
463 
464  bool is_valid (void) const { return get_face (); }
465 
466  FT_Face get_face (void) const;
467 
468  private:
469 
470  mutable FT_Face face;
471  };
472 
473  void push_new_line (void);
474 
475  void update_line_bbox (void);
476 
477  void compute_bbox (void);
478 
479  int compute_line_xoffset (const Matrix& lb) const;
480 
481  FT_UInt process_character (FT_ULong code, FT_UInt previous = 0);
482 
483  public:
484 
485  void text_to_strlist (const std::string& txt,
486  std::list<text_renderer::string>& lst, Matrix& bbox,
487  int halign, int valign, double rotation,
488  const caseless_str& interp);
489 
490  private:
491 
492  // The current font used by the renderer.
494 
495  // Used to stored the bounding box corresponding to the rendered text.
496  // The bounding box has the form [x, y, w, h] where x and y represent the
497  // coordinates of the bottom left corner relative to the anchor point of
498  // the text (== start of text on the baseline). Due to font descent or
499  // multiple lines, the value y is usually negative.
501 
502  // Used to stored the rendered text. It's a 3D matrix with size MxNx4
503  // where M and N are the width and height of the bounding box.
505 
506  // Used to store the bounding box of each line. This is used to layout
507  // multiline text properly.
508  std::list<Matrix> line_bbox;
509 
510  // The current horizontal alignment. This is used to align multi-line text.
511  int halign;
512 
513  // The X offset for the next glyph.
514  int xoffset;
515 
516  // The Y offset of the baseline for the current line.
518 
519  // The Y offset of the baseline for the next glyph. The offset is relative
520  // to line_yoffset. The total Y offset is computed with:
521  // line_yoffset + yoffset.
522  int yoffset;
523 
524  // The current mode of the rendering process (box computing or rendering).
525  int mode;
526 
527  // The base color of the rendered text.
529 
530  // A list of parsed strings to be used for printing.
531  std::list<text_renderer::string> strlist;
532 
533  // The X offset of the baseline for the current line.
535 
536  };
537 
538  void
540  const std::string& angle, double size)
541  {
542  // FIXME: take "fontunits" into account
543 
544  font = ft_font (name, weight, angle, size, nullptr);
545  }
546 
547  void
549  {
550  switch (mode)
551  {
552  case MODE_BBOX:
553  {
554  // Create a new bbox entry based on the current font.
555 
556  FT_Face face = font.get_face ();
557 
558  if (face)
559  {
560  int asc = face->size->metrics.ascender >> 6;
561  int desc = face->size->metrics.descender >> 6;
562  int h = face->size->metrics.height >> 6;
563 
564  Matrix bb (1, 5, 0.0);
565 
566  bb(1) = desc;
567  bb(3) = asc - desc;
568  bb(4) = h;
569 
570  line_bbox.push_back (bb);
571 
572  xoffset = yoffset = 0;
573  }
574  }
575  break;
576 
577  case MODE_RENDER:
578  {
579  // Move to the next line bbox, adjust xoffset based on alignment
580  // and yoffset based on the old and new line bbox.
581 
582  Matrix old_bbox = line_bbox.front ();
583  line_bbox.pop_front ();
584  Matrix new_bbox = line_bbox.front ();
585 
587  line_yoffset += (old_bbox(1) - (new_bbox(1) + new_bbox(3)));
588  yoffset = 0;
589  }
590  break;
591  }
592  }
593 
594  int
596  {
597  if (! bbox.isempty ())
598  {
599  switch (halign)
600  {
601  case 0:
602  return 0;
603  case 1:
604  return (bbox(2) - lb(2)) / 2;
605  case 2:
606  return (bbox(2) - lb(2));
607  }
608  }
609 
610  return 0;
611  }
612 
613  void
615  {
616  // Stack the various line bbox together and compute the final
617  // bounding box for the entire text string.
618 
619  bbox = Matrix ();
620 
621  switch (line_bbox.size ())
622  {
623  case 0:
624  break;
625 
626  case 1:
627  bbox = line_bbox.front ().extract (0, 0, 0, 3);
628  break;
629 
630  default:
631  for (const auto& lbox : line_bbox)
632  {
633  if (bbox.isempty ())
634  bbox = lbox.extract (0, 0, 0, 3);
635  else
636  {
637  bbox(1) -= lbox(3);
638  bbox(3) += lbox(3);
639  bbox(2) = math::max (bbox(2), lbox(2));
640  }
641  }
642  break;
643  }
644  }
645 
646  void
648  {
649  // Called after a font change, when in MODE_BBOX mode, to update the
650  // current line bbox with the new font metrics. This also includes the
651  // current yoffset, that is the offset of the current glyph's baseline
652  // the line's baseline.
653 
654  if (mode == MODE_BBOX)
655  {
656  int asc = font.get_face ()->size->metrics.ascender >> 6;
657  int desc = font.get_face ()->size->metrics.descender >> 6;
658 
659  Matrix& bb = line_bbox.back ();
660 
661  if ((yoffset + desc) < bb(1))
662  {
663  // The new font goes below the bottom of the current bbox.
664 
665  int delta = bb(1) - (yoffset + desc);
666 
667  bb(1) -= delta;
668  bb(3) += delta;
669  }
670 
671  if ((yoffset + asc) > (bb(1) + bb(3)))
672  {
673  // The new font goes above the top of the current bbox.
674 
675  int delta = (yoffset + asc) - (bb(1) + bb(3));
676 
677  bb(3) += delta;
678  }
679  }
680  }
681 
682  void
684  {
685  mode = m;
686 
687  switch (mode)
688  {
689  case MODE_BBOX:
690  xoffset = line_yoffset = yoffset = 0;
691  bbox = Matrix (1, 4, 0.0);
692  line_bbox.clear ();
693  push_new_line ();
694  break;
695 
696  case MODE_RENDER:
697  if (bbox.numel () != 4)
698  {
699  ::error ("ft_text_renderer: invalid bounding box, cannot render");
700 
701  xoffset = line_yoffset = yoffset = 0;
702  pixels = uint8NDArray ();
703  }
704  else
705  {
706  dim_vector d (4, octave_idx_type (bbox(2)),
707  octave_idx_type (bbox(3)));
708  pixels = uint8NDArray (d, static_cast<uint8_t> (0));
710  line_yoffset = -bbox(1)-1;
711  yoffset = 0;
712  }
713  break;
714 
715  default:
716  error ("ft_text_renderer: invalid mode '%d'", mode);
717  break;
718  }
719  }
720 
721  FT_UInt
722  ft_text_renderer::process_character (FT_ULong code, FT_UInt previous)
723  {
724  FT_Face face = font.get_face ();
725  FT_UInt glyph_index = 0;
726 
727  if (face)
728  {
729  glyph_index = FT_Get_Char_Index (face, code);
730 
731  if (code != '\n'
732  && (! glyph_index
733  || FT_Load_Glyph (face, glyph_index, FT_LOAD_DEFAULT)))
734  {
735  glyph_index = 0;
737  }
738  else
739  {
740  switch (mode)
741  {
742  case MODE_RENDER:
743  if (code == '\n')
744  {
745  glyph_index = FT_Get_Char_Index (face, ' ');
746  if (! glyph_index
747  || FT_Load_Glyph (face, glyph_index, FT_LOAD_DEFAULT))
748  {
749  glyph_index = 0;
750  warn_missing_glyph (' ');
751  }
752  else
753  push_new_line ();
754  }
755  else if (FT_Render_Glyph (face->glyph, FT_RENDER_MODE_NORMAL))
756  {
757  glyph_index = 0;
759  }
760  else
761  {
762  FT_Bitmap& bitmap = face->glyph->bitmap;
763  int x0, y0;
764 
765  if (previous)
766  {
767  FT_Vector delta;
768 
769  FT_Get_Kerning (face, previous, glyph_index,
770  FT_KERNING_DEFAULT, &delta);
771  xoffset += (delta.x >> 6);
772  }
773 
774  x0 = xoffset + face->glyph->bitmap_left;
775  y0 = line_yoffset + yoffset + face->glyph->bitmap_top;
776 
777  // 'w' seems to have a negative -1
778  // face->glyph->bitmap_left, this is so we don't
779  // index out of bound, and assumes we've allocated
780  // the right amount of horizontal space in the bbox.
781  if (x0 < 0)
782  x0 = 0;
783 
784  for (int r = 0; static_cast<unsigned int> (r) < bitmap.rows; r++)
785  for (int c = 0; static_cast<unsigned int> (c) < bitmap.width; c++)
786  {
787  unsigned char pix = bitmap.buffer[r*bitmap.width+c];
788  if (x0+c < 0 || x0+c >= pixels.dim2 ()
789  || y0-r < 0 || y0-r >= pixels.dim3 ())
790  {
791  //::warning ("ft_text_renderer: pixel out of bound (char=%d, (x,y)=(%d,%d), (w,h)=(%d,%d)",
792  // str[i], x0+c, y0-r, pixels.dim2 (), pixels.dim3 ());
793  }
794  else if (pixels(3, x0+c, y0-r).value () == 0)
795  {
796  pixels(0, x0+c, y0-r) = color(0);
797  pixels(1, x0+c, y0-r) = color(1);
798  pixels(2, x0+c, y0-r) = color(2);
799  pixels(3, x0+c, y0-r) = pix;
800  }
801  }
802 
803  xoffset += (face->glyph->advance.x >> 6);
804  }
805  break;
806 
807  case MODE_BBOX:
808  if (code == '\n')
809  {
810  glyph_index = FT_Get_Char_Index (face, ' ');
811  if (! glyph_index
812  || FT_Load_Glyph (face, glyph_index, FT_LOAD_DEFAULT))
813  {
814  glyph_index = 0;
815  warn_missing_glyph (' ');
816  }
817  else
818  push_new_line ();
819  }
820  else
821  {
822  Matrix& bb = line_bbox.back ();
823 
824  // If we have a previous glyph, use kerning information.
825  // This usually means moving a bit backward before adding
826  // the next glyph. That is, "delta.x" is usually < 0.
827  if (previous)
828  {
829  FT_Vector delta;
830 
831  FT_Get_Kerning (face, previous, glyph_index,
832  FT_KERNING_DEFAULT, &delta);
833 
834  xoffset += (delta.x >> 6);
835  }
836 
837  // Extend current X offset box by the width of the current
838  // glyph. Then extend the line bounding box if necessary.
839 
840  xoffset += (face->glyph->advance.x >> 6);
841  bb(2) = math::max (bb(2), xoffset);
842  }
843  break;
844  }
845  }
846  }
847 
848  return glyph_index;
849  }
850 
851  void
853  std::list<text_renderer::string>& lst,
854  Matrix& box,
855  int ha, int va, double rot,
856  const caseless_str& interp)
857  {
858  uint8NDArray pxls;
859 
860  // First run text_to_pixels which will also build the string list
861 
862  text_to_pixels (txt, pxls, box, ha, va, rot, interp, false);
863 
864  lst = strlist;
865  }
866 
867  void
869  {
870  if (font.is_valid ())
871  {
872  FT_UInt glyph_index, previous = 0;
873 
874  std::string str = e.string_value ();
875  size_t n = str.length ();
876  size_t curr = 0;
877  size_t idx = 0;
878  mbstate_t ps;
879  memset (&ps, 0, sizeof (ps)); // Initialize state to 0.
880  wchar_t wc;
881  std::string fname = font.get_face ()->family_name;
883  std::vector<double> xdata;
884 
885  while (n > 0)
886  {
887  size_t r = std::mbrtowc (&wc, str.data () + curr, n, &ps);
888 
889  if (r > 0
890  && r != static_cast<size_t> (-1)
891  && r != static_cast<size_t> (-2))
892  {
893  n -= r;
894  curr += r;
895 
896  if (wc == L'\n')
897  {
898  // Finish previous string in strlist before processing
899  // the newline character
900  fs.set_y (line_yoffset + yoffset);
901  fs.set_color (color);
902  std::string s = str.substr (idx, curr - idx - 1);
903  if (! s.empty ())
904  {
905  fs.set_string (s);
906  fs.set_xdata (xdata);
907  fs.set_family (fname);
908  strlist.push_back (fs);
909  }
910  }
911  else
912  xdata.push_back (xoffset);
913 
914  glyph_index = process_character (wc, previous);
915 
916  if (wc == L'\n')
917  {
918  previous = 0;
919  // Start a new string in strlist
920  idx = curr;
921  xdata.clear ();
922  fs = text_renderer::string (str.substr (idx), font,
924  }
925  else
926  previous = glyph_index;
927  }
928  else
929  {
930  if (r != 0)
931  ::warning ("ft_text_renderer: failed to decode string `%s' with "
932  "locale `%s'", str.c_str (),
933  std::setlocale (LC_CTYPE, nullptr));
934  break;
935  }
936  }
937 
938  if (! fs.get_string ().empty ())
939  {
940  fs.set_y (line_yoffset + yoffset);
941  fs.set_color (color);
942  fs.set_xdata (xdata);
943  fs.set_family (fname);
944  strlist.push_back (fs);
945  }
946  }
947  }
948 
949  void
951  {
952  // Save and restore (after processing the list) the current font and color.
953 
954  ft_font saved_font (font);
955  uint8NDArray saved_color (color);
956 
958 
959  font = saved_font;
960  color = saved_color;
961  }
962 
963  void
965  {
966  ft_font saved_font (font);
967  int saved_line_yoffset = line_yoffset;
968  int saved_yoffset = yoffset;
969 
971  font.get_size () - 2);
972 
973  if (font.is_valid ())
974  {
975  int h = font.get_face ()->size->metrics.height >> 6;
976 
977  // Shifting the baseline by 2/3 the font height seems to produce
978  // decent result.
979  yoffset -= (h * 2) / 3;
980 
981  if (mode == MODE_BBOX)
982  update_line_bbox ();
983  }
984 
986 
987  font = saved_font;
988  // If line_yoffset changed, this means we moved to a new line; hence yoffset
989  // cannot be restored, because the saved value is not relevant anymore.
990  if (line_yoffset == saved_line_yoffset)
991  yoffset = saved_yoffset;
992  }
993 
994  void
996  {
997  ft_font saved_font (font);
998  int saved_line_yoffset = line_yoffset;
999  int saved_yoffset = yoffset;
1000 
1002  font.get_size () - 2);
1003 
1004  if (saved_font.is_valid ())
1005  {
1006  int s_asc = saved_font.get_face ()->size->metrics.ascender >> 6;
1007 
1008  // Shifting the baseline by 2/3 base font ascender seems to produce
1009  // decent result.
1010  yoffset += (s_asc * 2) / 3;
1011 
1012  if (mode == MODE_BBOX)
1013  update_line_bbox ();
1014  }
1015 
1017 
1018  font = saved_font;
1019  // If line_yoffset changed, this means we moved to a new line; hence yoffset
1020  // cannot be restored, because the saved value is not relevant anymore.
1021  if (line_yoffset == saved_line_yoffset)
1022  yoffset = saved_yoffset;
1023  }
1024 
1025  void
1027  {
1028  if (mode == MODE_RENDER)
1029  set_color (e.get_color ());
1030  }
1031 
1032  void
1034  {
1035  double sz = e.get_fontsize ();
1036 
1037  // FIXME: Matlab documentation says that the font size is expressed
1038  // in the text object FontUnit.
1039 
1041 
1042  if (mode == MODE_BBOX)
1043  update_line_bbox ();
1044  }
1045 
1046  void
1048  {
1049  set_font (e.get_fontname (), font.get_weight (), font.get_angle (),
1050  font.get_size ());
1051 
1052  if (mode == MODE_BBOX)
1053  update_line_bbox ();
1054  }
1055 
1056  void
1058  {
1059  switch (e.get_fontstyle ())
1060  {
1062  set_font (font.get_name (), "normal", "normal", font.get_size ());
1063  break;
1064 
1066  set_font (font.get_name (), "bold", "normal", font.get_size ());
1067  break;
1068 
1070  set_font (font.get_name (), "normal", "italic", font.get_size ());
1071  break;
1072 
1074  set_font (font.get_name (), "normal", "oblique", font.get_size ());
1075  break;
1076  }
1077 
1078  if (mode == MODE_BBOX)
1079  update_line_bbox ();
1080  }
1081 
1082  void
1084  {
1085  uint32_t code = e.get_symbol_code ();
1086 
1087  std::vector<double> xdata (1, xoffset);
1089 
1091  {
1093  fs.set_code (code);
1094  fs.set_xdata (xdata);
1095  }
1096  else if (font.is_valid ())
1097  ::warning ("ignoring unknown symbol: %d", e.get_symbol ());
1098 
1099  if (fs.get_code ())
1100  {
1101  fs.set_y (line_yoffset + yoffset);
1102  fs.set_color (color);
1103  fs.set_family (font.get_face ()->family_name);
1104  strlist.push_back (fs);
1105  }
1106  }
1107 
1108  void
1110  {
1111  int saved_xoffset = xoffset;
1112  int max_xoffset = xoffset;
1113 
1114  for (auto *txt_elt : e)
1115  {
1116  xoffset = saved_xoffset;
1117  txt_elt->accept (*this);
1118  max_xoffset = math::max (xoffset, max_xoffset);
1119  }
1120 
1121  xoffset = max_xoffset;
1122  }
1123 
1124  void
1126  {
1127  set_mode (MODE_BBOX);
1128  set_color (Matrix (1, 3, 0.0));
1129  }
1130 
1131  void
1133  {
1134  if (c.numel () == 3)
1135  {
1136  color(0) = static_cast<uint8_t> (c(0)*255);
1137  color(1) = static_cast<uint8_t> (c(1)*255);
1138  color(2) = static_cast<uint8_t> (c(2)*255);
1139  }
1140  else
1141  ::warning ("ft_text_renderer::set_color: invalid color");
1142  }
1143 
1144  uint8NDArray
1146  {
1147  set_mode (MODE_BBOX);
1148  elt->accept (*this);
1149  compute_bbox ();
1150  box = bbox;
1151 
1153  // Clear the list of parsed strings
1154  strlist.clear ();
1155 
1156  if (pixels.numel () > 0)
1157  {
1158  elt->accept (*this);
1159 
1160  switch (rotation)
1161  {
1162  case ROTATION_0:
1163  break;
1164 
1165  case ROTATION_90:
1166  {
1167  Array<octave_idx_type> perm (dim_vector (3, 1));
1168  perm(0) = 0;
1169  perm(1) = 2;
1170  perm(2) = 1;
1171  pixels = pixels.permute (perm);
1172 
1173  Array<idx_vector> idx (dim_vector (3, 1));
1174  idx(0) = idx_vector (':');
1175  idx(1) = idx_vector (pixels.dim2 ()-1, -1, -1);
1176  idx(2) = idx_vector (':');
1177  pixels = uint8NDArray (pixels.index (idx));
1178  }
1179  break;
1180 
1181  case ROTATION_180:
1182  {
1183  Array<idx_vector> idx (dim_vector (3, 1));
1184  idx(0) = idx_vector (':');
1185  idx(1) = idx_vector (pixels.dim2 ()-1, -1, -1);
1186  idx(2) = idx_vector (pixels.dim3 ()-1, -1, -1);
1187  pixels = uint8NDArray (pixels.index (idx));
1188  }
1189  break;
1190 
1191  case ROTATION_270:
1192  {
1193  Array<octave_idx_type> perm (dim_vector (3, 1));
1194  perm(0) = 0;
1195  perm(1) = 2;
1196  perm(2) = 1;
1197  pixels = pixels.permute (perm);
1198 
1199  Array<idx_vector> idx (dim_vector (3, 1));
1200  idx(0) = idx_vector (':');
1201  idx(1) = idx_vector (':');
1202  idx(2) = idx_vector (pixels.dim3 ()-1, -1, -1);
1203  pixels = uint8NDArray (pixels.index (idx));
1204  }
1205  break;
1206  }
1207  }
1208 
1209  return pixels;
1210  }
1211 
1212  // Note:
1213  // x-extent accurately measures width of glyphs.
1214  // y-extent is overly large because it is measured from baseline-to-baseline.
1215  // Calling routines, such as ylabel, may need to account for this mismatch.
1216 
1217  Matrix
1219  {
1220  set_mode (MODE_BBOX);
1221  elt->accept (*this);
1222  compute_bbox ();
1223 
1224  Matrix extent (1, 2, 0.0);
1225 
1226  switch (rotation_to_mode (rotation))
1227  {
1228  case ROTATION_0:
1229  case ROTATION_180:
1230  extent(0) = bbox(2);
1231  extent(1) = bbox(3);
1232  break;
1233 
1234  case ROTATION_90:
1235  case ROTATION_270:
1236  extent(0) = bbox(3);
1237  extent(1) = bbox(2);
1238  }
1239 
1240  return extent;
1241  }
1242 
1243  Matrix
1244  ft_text_renderer::get_extent (const std::string& txt, double rotation,
1245  const caseless_str& interpreter)
1246  {
1248  Matrix extent = get_extent (elt, rotation);
1249  delete elt;
1250 
1251  return extent;
1252  }
1253 
1254  int
1255  ft_text_renderer::rotation_to_mode (double rotation) const
1256  {
1257  // Clip rotation to range [0, 360]
1258  while (rotation < 0)
1259  rotation += 360.0;
1260  while (rotation > 360.0)
1261  rotation -= 360.0;
1262 
1263  if (rotation == 0.0)
1264  return ROTATION_0;
1265  else if (rotation == 90.0)
1266  return ROTATION_90;
1267  else if (rotation == 180.0)
1268  return ROTATION_180;
1269  else if (rotation == 270.0)
1270  return ROTATION_270;
1271  else
1272  return ROTATION_0;
1273  }
1274 
1275  void
1277  uint8NDArray& pxls, Matrix& box,
1278  int _halign, int valign, double rotation,
1279  const caseless_str& interpreter,
1280  bool handle_rotation)
1281  {
1282  int rot_mode = rotation_to_mode (rotation);
1283 
1284  halign = _halign;
1285 
1287  pxls = render (elt, box, rot_mode);
1288  delete elt;
1289 
1290  if (pxls.isempty ())
1291  return; // nothing to render
1292 
1293  switch (halign)
1294  {
1295  case 1:
1296  box(0) = -box(2)/2;
1297  break;
1298 
1299  case 2:
1300  box(0) = -box(2);
1301  break;
1302 
1303  default:
1304  box(0) = 0;
1305  break;
1306  }
1307 
1308  switch (valign)
1309  {
1310  case 1:
1311  box(1) = -box(3)/2;
1312  break;
1313 
1314  case 2:
1315  box(1) = -box(3);
1316  break;
1317 
1318  case 3:
1319  break;
1320 
1321  case 4:
1322  box(1) = -box(3)-box(1);
1323  break;
1324 
1325  default:
1326  box(1) = 0;
1327  break;
1328  }
1329 
1330  if (handle_rotation)
1331  {
1332  switch (rot_mode)
1333  {
1334  case ROTATION_90:
1335  std::swap (box(0), box(1));
1336  std::swap (box(2), box(3));
1337  box(0) = -box(0)-box(2);
1338  break;
1339 
1340  case ROTATION_180:
1341  box(0) = -box(0)-box(2);
1342  box(1) = -box(1)-box(3);
1343  break;
1344 
1345  case ROTATION_270:
1346  std::swap (box(0), box(1));
1347  std::swap (box(2), box(3));
1348  box(1) = -box(1)-box(3);
1349  break;
1350  }
1351  }
1352  }
1353 
1355  : text_renderer::font (ft), face (nullptr)
1356  {
1357 #if defined (HAVE_FT_REFERENCE_FACE)
1358  FT_Face ft_face = ft.get_face ();
1359 
1360  if (ft_face && FT_Reference_Face (ft_face) == 0)
1361  face = ft_face;
1362 #endif
1363  }
1364 
1367  {
1368  if (&ft != this)
1369  {
1371 
1372  if (face)
1373  {
1374  FT_Done_Face (face);
1375  face = nullptr;
1376  }
1377 
1378 #if defined (HAVE_FT_REFERENCE_FACE)
1379  FT_Face ft_face = ft.get_face ();
1380 
1381  if (ft_face && FT_Reference_Face (ft_face) == 0)
1382  face = ft_face;
1383 #endif
1384  }
1385 
1386  return *this;
1387  }
1388 
1389  FT_Face
1391  {
1392  if (! face && ! name.empty ())
1393  {
1394  face = ft_manager::get_font (name, weight, angle, size);
1395 
1396  if (face)
1397  {
1398  if (FT_Set_Char_Size (face, 0, size*64, 0, 0))
1399  ::warning ("ft_text_renderer: unable to set font size to %g", size);
1400  }
1401  else
1402  ::warning ("ft_text_renderer: unable to load appropriate font");
1403  }
1404 
1405  return face;
1406  }
1407 }
1408 
1409 #endif
1410 
1411 namespace octave
1412 {
1415  {
1416 #if defined (HAVE_FREETYPE)
1417  return new ft_text_renderer ();
1418 #else
1419  return 0;
1420 #endif
1421  }
1422 }
ft_font & operator=(const ft_font &ft)
void warning_with_id(const char *id, const char *fmt,...)
Definition: error.cc:816
std::string oct_fonts_dir(void)
Definition: defaults.cc:299
static FT_Face get_font(const std::string &name, const std::string &weight, const std::string &angle, double size)
For example cd octave end example noindent changes the current working directory to file
Definition: dirfns.cc:124
int rotation_to_mode(double rotation) const
octave_idx_type dim3(void) const
Definition: Array.h:420
fname
Definition: load-save.cc:767
double get_size(void) const
uint8NDArray render(text_element *elt, Matrix &box, int rotation=ROTATION_0)
bool isempty(void) const
Definition: Array.h:565
F77_RET_T const F77_REAL const F77_REAL F77_REAL &F77_RET_T const F77_DBLE const F77_DBLE F77_DBLE &F77_RET_T const F77_DBLE F77_DBLE &F77_RET_T const F77_REAL F77_REAL &F77_RET_T const F77_DBLE const F77_DBLE * f
std::pair< std::string, double > ft_key
Return the CPU time used by your Octave session The first output is the total time spent executing your process and is equal to the sum of second and third which are the number of CPU seconds spent executing in user mode and the number of CPU seconds spent executing in system mode
Definition: data.cc:6348
std::map< ft_key, FT_Face > ft_cache
intNDArray< octave_uint8 > uint8NDArray
Definition: uint8NDArray.h:33
std::list< Matrix > line_bbox
T max(T x, T y)
Definition: lo-mappers.h:383
std::string get_name(void) const
static ft_manager * instance
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)
void set_color(const Matrix &c)
void error(const char *fmt,...)
Definition: error.cc:578
nd example oindent opens the file binary numeric values will be read assuming they are stored in IEEE format with the least significant bit and then converted to the native representation Opening a file that is already open simply opens it again and returns a separate file id It is not an error to open a file several though writing to the same file through several different file ids may produce unexpected results The possible values of text mode reading and writing automatically converts linefeeds to the appropriate line end character for the you may append a you must also open the file in binary mode The parameter conversions are currently only supported for and permissions will be set to and then everything is written in a single operation This is very efficient and improves performance c
Definition: file-io.cc:587
s
Definition: file-io.cc:2729
i e
Definition: data.cc:2591
FT_Face do_get_font(const std::string &name, const std::string &weight, const std::string &angle, double size)
std::string dir_sep_str(void)
Definition: file-ops.cc:233
F77_RET_T const F77_REAL const F77_REAL F77_REAL &F77_RET_T const F77_DBLE const F77_DBLE F77_DBLE &F77_RET_T const F77_DBLE F77_DBLE &F77_RET_T const F77_REAL F77_REAL &F77_RET_T const F77_DBLE const F77_DBLE F77_DBLE * d
static std::string getenv(const std::string &name)
Definition: oct-env.cc:235
std::list< text_renderer::string > strlist
bool swap
Definition: load-save.cc:738
MArray< T > permute(const Array< octave_idx_type > &vec, bool inv=false) const
Definition: MArray.h:94
base_text_renderer * make_ft_text_renderer(void)
static void warn_missing_glyph(FT_ULong c)
double h
Definition: graphics.cc:11808
nd deftypefn *std::string name
Definition: sysdep.cc:647
Array< T > index(const idx_vector &i) const
Indexing without resizing.
Definition: Array.cc:697
OCTAVE_EXPORT octave_value_list isdir nd deftypefn *std::string nm
Definition: utils.cc:975
static void ft_face_destroyed(void *object)
static octave_value box(JNIEnv *jni_env, void *jobj, void *jcls_arg=nullptr)
uint8NDArray get_pixels(void) const
static void add(fptr f)
int compute_line_xoffset(const Matrix &lb) const
std::string str
Definition: hash.cc:118
virtual void accept(text_processor &p)=0
octave_idx_type dim2(void) const
Definition: Array.h:411
void visit(text_element_string &e)
Matrix get_extent(text_element *elt, double rotation=0.0)
std::string get_weight(void) const
double tmp
Definition: data.cc:6252
is false
Definition: cellfun.cc:400
octave_value retval
Definition: data.cc:6246
do not permute tem code
Definition: balance.cc:90
virtual text_element * parse(const std::string &s)=0
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)
Definition: dMatrix.h:36
sz
Definition: data.cc:5264
std::string get_angle(void) const
Matrix extract(octave_idx_type r1, octave_idx_type c1, octave_idx_type r2, octave_idx_type c2) const
Definition: dMatrix.cc:396
static void warn_glyph_render(FT_ULong c)
void warning(const char *fmt,...)
Definition: error.cc:801
defaults to zero A value of zero computes the digamma a value the trigamma and so on The digamma function is defined
Definition: psi.cc:68
void do_font_destroyed(FT_Face face)
font & operator=(const font &ft)
Definition: text-renderer.h:97
FT_UInt process_character(FT_ULong code, FT_UInt previous=0)
static bool instance_ok(void)
octave::sys::file_stat fs(filename)
void set_font(const std::string &name, const std::string &weight, const std::string &angle, double size)
octave_idx_type numel(void) const
Number of elements in the array.
Definition: Array.h:366
static void cleanup_instance(void)
virtual void visit(text_element_string &e)=0
Vector representing the dimensions (size) of an Array.
Definition: dim-vector.h:87
Matrix get_boundingbox(void) const
ft_font(const std::string &nm, const std::string &wt, const std::string &ang, double sz, FT_Face f=nullptr)
static void font_destroyed(FT_Face face)
If this string is the system will ring the terminal sometimes it is useful to be able to print the original representation of the string
Definition: utils.cc:888