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