Panda3D
dynamicTextFont.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file dynamicTextFont.cxx
10  * @author drose
11  * @date 2002-02-08
12  */
13 
14 #include "dynamicTextFont.h"
15 
16 #ifdef HAVE_FREETYPE
17 
18 #undef interface // I don't know where this symbol is defined, but it interferes with FreeType.
19 #include FT_OUTLINE_H
20 #ifdef FT_BBOX_H
21 #include FT_BBOX_H
22 #endif
23 #ifdef FT_BITMAP_H
24 #include FT_BITMAP_H
25 #endif
26 #ifdef FT_STROKER_H
27 #include FT_STROKER_H
28 #endif
29 
30 #include "config_text.h"
31 #include "config_putil.h"
32 #include "config_express.h"
33 #include "virtualFileSystem.h"
34 #include "geomVertexData.h"
35 #include "geomVertexFormat.h"
36 #include "geomVertexWriter.h"
37 #include "geomLinestrips.h"
38 #include "geomTriangles.h"
39 #include "renderState.h"
40 #include "string_utils.h"
41 #include "triangulator.h"
42 // #include "renderModeAttrib.h" #include "antialiasAttrib.h"
43 #include "colorAttrib.h"
44 #include "textureAttrib.h"
45 #include "transparencyAttrib.h"
46 
47 #ifdef HAVE_HARFBUZZ
48 #include <hb-ft.h>
49 #endif
50 
51 TypeHandle DynamicTextFont::_type_handle;
52 
53 
54 /**
55  * The constructor expects the name of some font file that FreeType can read,
56  * along with face_index, indicating which font within the file to load
57  * (usually 0).
58  */
59 DynamicTextFont::
60 DynamicTextFont(const Filename &font_filename, int face_index) {
61  initialize();
62  _is_valid = load_font(font_filename, face_index);
63  TextFont::set_name(FreetypeFont::get_name());
64  TextFont::_line_height = FreetypeFont::get_line_height();
65  TextFont::_space_advance = FreetypeFont::get_space_advance();
66 
67  _fg.set(1.0f, 1.0f, 1.0f, 1.0f);
68  _bg.set(1.0f, 1.0f, 1.0f, 0.0f);
69  _outline_color.set(1.0f, 1.0f, 1.0f, 0.0f);
70  _outline_width = 0.0f;
71  _outline_feather = 0.0f;
72  _has_outline = false;
73  _tex_format = Texture::F_alpha;
74  _needs_image_processing = false;
75 }
76 
77 /**
78  * This constructor accepts a table of data representing the font file, loaded
79  * from some source other than a filename on disk.
80  */
81 DynamicTextFont::
82 DynamicTextFont(const char *font_data, int data_length, int face_index) {
83  initialize();
84  _is_valid = load_font(font_data, data_length, face_index);
85  TextFont::set_name(FreetypeFont::get_name());
86  TextFont::_line_height = FreetypeFont::_line_height;
87  TextFont::_space_advance = FreetypeFont::_space_advance;
88 
89  _fg.set(1.0f, 1.0f, 1.0f, 1.0f);
90  _bg.set(1.0f, 1.0f, 1.0f, 0.0f);
91  _outline_color.set(1.0f, 1.0f, 1.0f, 0.0f);
92  _outline_width = 0.0f;
93  _outline_feather = 0.0f;
94  _has_outline = false;
95  _tex_format = Texture::F_alpha;
96  _needs_image_processing = false;
97 }
98 
99 /**
100  *
101  */
102 DynamicTextFont::
103 DynamicTextFont(const DynamicTextFont &copy) :
104  TextFont(copy),
105  FreetypeFont(copy),
106  _texture_margin(copy._texture_margin),
107  _poly_margin(copy._poly_margin),
108  _page_size(copy._page_size),
109  _minfilter(copy._minfilter),
110  _magfilter(copy._magfilter),
111  _anisotropic_degree(copy._anisotropic_degree),
112  _render_mode(copy._render_mode),
113  _fg(copy._fg),
114  _bg(copy._bg),
115  _outline_color(copy._outline_color),
116  _outline_width(copy._outline_width),
117  _outline_feather(copy._outline_feather),
118  _has_outline(copy._has_outline),
119  _tex_format(copy._tex_format),
120  _needs_image_processing(copy._needs_image_processing),
121  _preferred_page(0),
122  _hb_font(nullptr)
123 {
124 }
125 
126 /**
127  *
128  */
129 DynamicTextFont::
130 ~DynamicTextFont() {
131 #ifdef HAVE_HARFBUZZ
132  if (_hb_font != nullptr) {
133  hb_font_destroy(_hb_font);
134  }
135 #endif
136 }
137 
138 /**
139  * Returns a new copy of the same font.
140  */
141 PT(TextFont) DynamicTextFont::
142 make_copy() const {
143  return new DynamicTextFont(*this);
144 }
145 
146 /**
147  * Returns the number of pages associated with the font. Initially, the font
148  * has zero pages; when the first piece of text is rendered with the font, it
149  * will add additional pages as needed. Each page is a Texture object that
150  * contains the images for each of the glyphs currently in use somewhere.
151  */
152 int DynamicTextFont::
153 get_num_pages() const {
154  return _pages.size();
155 }
156 
157 /**
158  * Returns the nth page associated with the font. Initially, the font has
159  * zero pages; when the first piece of text is rendered with the font, it will
160  * add additional pages as needed. Each page is a Texture object that
161  * contains the images for each of the glyphs currently in use somewhere.
162  */
163 DynamicTextPage *DynamicTextFont::
164 get_page(int n) const {
165  nassertr(n >= 0 && n < (int)_pages.size(), nullptr);
166  return _pages[n];
167 }
168 
169 /**
170  * Removes all of the glyphs from the font that are no longer being used by
171  * any Geoms. Returns the number of glyphs removed.
172  */
173 int DynamicTextFont::
174 garbage_collect() {
175  int removed_count = 0;
176 
177  // First, remove all the old entries from our cache index.
178  Cache new_cache;
179  Cache::iterator ci;
180  for (ci = _cache.begin(); ci != _cache.end(); ++ci) {
181  const TextGlyph *glyph = (*ci).second;
182  if (glyph == nullptr || glyph->get_ref_count() > 1) {
183  // Keep this one.
184  new_cache.insert(new_cache.end(), (*ci));
185  } else {
186  // Drop this one.
187  removed_count++;
188  }
189  }
190  _cache.swap(new_cache);
191 
192  // Now, go through each page and do the same thing.
193  Pages::iterator pi;
194  for (pi = _pages.begin(); pi != _pages.end(); ++pi) {
195  DynamicTextPage *page = (*pi);
196  page->garbage_collect(this);
197  }
198 
199  return removed_count;
200 }
201 
202 /**
203  * Drops all the glyphs out of the cache and frees any association with any
204  * previously-generated pages.
205  *
206  * Calling this frequently can result in wasted texture memory, as any
207  * previously rendered text will still keep a pointer to the old, previously-
208  * generated pages. As long as the previously rendered text remains around,
209  * the old pages will also remain around.
210  */
211 void DynamicTextFont::
212 clear() {
213  _cache.clear();
214  _pages.clear();
215  _empty_glyphs.clear();
216 
217 #ifdef HAVE_HARFBUZZ
218  if (_hb_font != nullptr) {
219  hb_font_destroy(_hb_font);
220  _hb_font = nullptr;
221  }
222 #endif
223 }
224 
225 /**
226  *
227  */
228 void DynamicTextFont::
229 write(std::ostream &out, int indent_level) const {
230  static const int max_glyph_name = 1024;
231  char glyph_name[max_glyph_name];
232 
233  indent(out, indent_level)
234  << "DynamicTextFont " << get_name() << ", "
235  << get_num_pages() << " pages, "
236  << _cache.size() << " glyphs:\n";
237  Cache::const_iterator ci;
238  for (ci = _cache.begin(); ci != _cache.end(); ++ci) {
239  int glyph_index = (*ci).first;
240  indent(out, indent_level + 2)
241  << glyph_index;
242 
243  FT_Face face = acquire_face();
244  if (FT_HAS_GLYPH_NAMES(face)) {
245  int error = FT_Get_Glyph_Name(face, glyph_index,
246  glyph_name, max_glyph_name);
247 
248  // Some fonts, notably MS Mincho, claim to have glyph names but only
249  // report ".notdef" as the name of each glyph. Thanks.
250  if (!error && strcmp(glyph_name, ".notdef") != 0) {
251  out << " (" << glyph_name << ")";
252  }
253  }
254  release_face(face);
255 
256  out << '\n';
257  }
258 }
259 
260 /**
261  * Gets the glyph associated with the given character code, as well as an
262  * optional scaling parameter that should be applied to the glyph's geometry
263  * and advance parameters. Returns true if the glyph exists, false if it does
264  * not. Even if the return value is false, the value for glyph might be
265  * filled in with a printable glyph.
266  */
267 bool DynamicTextFont::
268 get_glyph(int character, CPT(TextGlyph) &glyph) {
269  if (!_is_valid) {
270  glyph = nullptr;
271  return false;
272  }
273 
274  FT_Face face = acquire_face();
275  int glyph_index = FT_Get_Char_Index(face, character);
276  if (text_cat.is_spam()) {
277  text_cat.spam()
278  << *this << " maps " << character << " to glyph " << glyph_index << "\n";
279  }
280 
281  Cache::iterator ci = _cache.find(glyph_index);
282  if (ci != _cache.end()) {
283  glyph = (*ci).second;
284  } else {
285  glyph = make_glyph(character, face, glyph_index);
286  _cache.insert(Cache::value_type(glyph_index, glyph.p()));
287  }
288 
289  if (glyph.is_null()) {
290  glyph = get_invalid_glyph();
291  glyph_index = 0;
292  }
293 
294  release_face(face);
295  return (glyph_index != 0);
296 }
297 
298 /**
299  * Returns the amount by which to offset the second glyph when it directly
300  * follows the first glyph. This is an additional offset that is added on top
301  * of the advance.
302  */
303 PN_stdfloat DynamicTextFont::
304 get_kerning(int first, int second) const {
305  if (!_is_valid) {
306  return 0;
307  }
308 
309  FT_Face face = acquire_face();
310  if (!FT_HAS_KERNING(face)) {
311  release_face(face);
312  return 0;
313  }
314 
315  int first_index = FT_Get_Char_Index(face, first);
316  int second_index = FT_Get_Char_Index(face, second);
317 
318  FT_Vector delta;
319  FT_Get_Kerning(face, first_index, second_index, FT_KERNING_DEFAULT, &delta);
320  release_face(face);
321 
322  return delta.x / (_font_pixels_per_unit * 64);
323 }
324 
325 /**
326  * Like get_glyph, but uses a glyph index.
327  */
328 bool DynamicTextFont::
329 get_glyph_by_index(int character, int glyph_index, CPT(TextGlyph) &glyph) {
330  if (!_is_valid) {
331  glyph = nullptr;
332  return false;
333  }
334 
335  Cache::iterator ci = _cache.find(glyph_index);
336  if (ci != _cache.end()) {
337  glyph = (*ci).second;
338  } else {
339  FT_Face face = acquire_face();
340  glyph = make_glyph(character, face, glyph_index);
341  _cache.insert(Cache::value_type(glyph_index, glyph.p()));
342  release_face(face);
343  }
344 
345  if (glyph.is_null()) {
346  glyph = get_invalid_glyph();
347  return false;
348  }
349 
350  return true;
351 }
352 
353 /**
354  * If Panda was compiled with HarfBuzz enabled, returns a HarfBuzz font for
355  * this font.
356  */
357 hb_font_t *DynamicTextFont::
358 get_hb_font() const {
359 #ifdef HAVE_HARFBUZZ
360  if (_hb_font != nullptr) {
361  return _hb_font;
362  }
363 
364  FT_Face face = acquire_face();
365  _hb_font = hb_ft_font_create(face, nullptr);
366  release_face(face);
367 
368  return _hb_font;
369 #else
370  return nullptr;
371 #endif
372 }
373 
374 /**
375  * Called from both constructors to set up some initial values.
376  */
377 void DynamicTextFont::
378 initialize() {
379  _texture_margin = text_texture_margin;
380  _poly_margin = text_poly_margin;
381  _page_size.set(text_page_size[0], text_page_size[1]);
382 
383  // We don't necessarily want to use mipmaps, since we don't want to
384  // regenerate those every time the texture changes, but we probably do want
385  // at least linear filtering. Use whatever the Configrc file suggests.
386  _minfilter = text_minfilter;
387  _magfilter = text_magfilter;
388 
389  // Anisotropic filtering can help the look of the text, and doesn't require
390  // generating mipmaps, but does require hardware support.
391  _anisotropic_degree = text_anisotropic_degree;
392 
393  _render_mode = text_render_mode;
394  _winding_order = WO_default;
395 
396  _preferred_page = 0;
397 
398  _hb_font = nullptr;
399 }
400 
401 /**
402  * Reapplies all current filter settings to all of the pages. This is
403  * normally called whenever the filter settings change.
404  */
405 void DynamicTextFont::
406 update_filters() {
407  Pages::iterator pi;
408  for (pi = _pages.begin(); pi != _pages.end(); ++pi) {
409  DynamicTextPage *page = (*pi);
410  page->set_minfilter(_minfilter);
411  page->set_magfilter(_magfilter);
412  page->set_anisotropic_degree(_anisotropic_degree);
413  }
414 }
415 
416 /**
417  * Examines the _fg, _bg, and _outline colors to determine the appropriate
418  * format for the font pages, including the outline properties.
419  */
420 void DynamicTextFont::
421 determine_tex_format() {
422  nassertv(get_num_pages() == 0);
423 
424  _has_outline = (_outline_color != _bg && _outline_width > 0.0f);
425  _needs_image_processing = true;
426 
427  bool needs_color = false;
428  bool needs_grayscale = false;
429  bool needs_alpha = false;
430 
431  if (_fg[1] != _fg[0] || _fg[2] != _fg[0] ||
432  _bg[1] != _bg[0] || _bg[2] != _bg[0] ||
433  (_has_outline && (_outline_color[1] != _outline_color[0] || _outline_color[2] != _outline_color[0]))) {
434  // At least one of fg, bg, or outline contains a color, not just a
435  // grayscale value.
436  needs_color = true;
437 
438  } else if (_fg[0] != 1.0f || _fg[1] != 1.0f || _fg[2] != 1.0f ||
439  _bg[0] != 1.0f || _bg[1] != 1.0f || _bg[2] != 1.0f ||
440  (_has_outline && (_outline_color[0] != 1.0f || _outline_color[1] != 1.0f || _outline_color[2] != 1.0f))) {
441  // fg, bg, and outline contain non-white grayscale values.
442  needs_grayscale = true;
443  }
444 
445  if (_fg[3] != 1.0f || _bg[3] != 1.0f ||
446  (_has_outline && (_outline_color[3] != 1.0f))) {
447  // fg, bg, and outline contain non-opaque alpha values.
448  needs_alpha = true;
449  }
450 
451  if (needs_color) {
452  if (needs_alpha) {
453  _tex_format = Texture::F_rgba;
454  } else {
455  _tex_format = Texture::F_rgb;
456  }
457  } else if (needs_grayscale) {
458  if (needs_alpha) {
459  _tex_format = Texture::F_luminance_alpha;
460  } else {
461  _tex_format = Texture::F_luminance;
462  }
463  } else {
464  if (needs_alpha) {
465  _tex_format = Texture::F_alpha;
466 
467  if (!_has_outline &&
468  _fg == LColor(1.0f, 1.0f, 1.0f, 1.0f) &&
469  _bg == LColor(1.0f, 1.0f, 1.0f, 0.0f)) {
470  // This is the standard font color. It can be copied directly without
471  // any need for special processing.
472  _needs_image_processing = false;
473  }
474 
475  } else {
476  // This won't be a very interesting font.
477  _tex_format = Texture::F_luminance;
478  }
479  }
480 }
481 
482 /**
483  * Slots a space in the texture map for the new character and renders the
484  * glyph, returning the newly-created TextGlyph object, or NULL if the glyph
485  * cannot be created for some reason.
486  */
487 CPT(TextGlyph) DynamicTextFont::
488 make_glyph(int character, FT_Face face, int glyph_index) {
489  if (!load_glyph(face, glyph_index, false)) {
490  return nullptr;
491  }
492 
493  FT_GlyphSlot slot = face->glyph;
494  FT_Bitmap &bitmap = slot->bitmap;
495 
496  if ((bitmap.width == 0 || bitmap.rows == 0) && (glyph_index == 0)) {
497  // Here's a special case: a glyph_index of 0 means an invalid glyph. Some
498  // fonts define a symbol to represent an invalid glyph, but if that symbol
499  // is the empty bitmap, we return NULL, and use Panda's invalid glyph in
500  // its place. We do this to guarantee that every invalid glyph is visible
501  // as *something*.
502  return nullptr;
503  }
504 
505  PN_stdfloat advance = slot->advance.x / 64.0;
506  advance /= _font_pixels_per_unit;
507 
508  if (_render_mode != RM_texture &&
509  slot->format == ft_glyph_format_outline) {
510  // Re-stroke the glyph to make it an outline glyph.
511  /*
512  FT_Stroker stroker;
513  FT_Stroker_New(face->memory, &stroker);
514  FT_Stroker_Set(stroker, 16 * 16, FT_STROKER_LINECAP_BUTT,
515  FT_STROKER_LINEJOIN_ROUND, 0);
516 
517  FT_Stroker_ParseOutline(stroker, &slot->outline, 0);
518 
519  FT_UInt num_points, num_contours;
520  FT_Stroker_GetCounts(stroker, &num_points, &num_contours);
521 
522  FT_Outline border;
523  FT_Outline_New(_ft_library, num_points, num_contours, &border);
524  border.n_points = 0;
525  border.n_contours = 0;
526  FT_Stroker_Export(stroker, &border);
527  FT_Stroker_Done(stroker);
528 
529  FT_Outline_Done(_ft_library, &slot->outline);
530  memcpy(&slot->outline, &border, sizeof(border));
531  */
532 
533  // Ask FreeType to extract the contours out of the outline description.
534  decompose_outline(slot->outline);
535 
536  PT(TextGlyph) glyph =
537  new TextGlyph(character, advance);
538  switch (_render_mode) {
539  case RM_wireframe:
540  render_wireframe_contours(glyph);
541  return glyph;
542 
543  case RM_polygon:
544  render_polygon_contours(glyph, true, false);
545  return glyph;
546 
547  case RM_extruded:
548  render_polygon_contours(glyph, false, true);
549  return glyph;
550 
551  case RM_solid:
552  render_polygon_contours(glyph, true, true);
553  return glyph;
554 
555  case RM_texture:
556  case RM_distance_field:
557  default:
558  break;
559  }
560  }
561 
562  PN_stdfloat tex_x_size, tex_y_size, tex_x_orig, tex_y_orig;
563  FT_BBox bounds;
564  TransparencyAttrib::Mode alpha_mode;
565 
566  if (_render_mode == RM_texture) {
567  // Render the glyph if necessary.
568  if (slot->format != ft_glyph_format_bitmap) {
569  FT_Render_Glyph(slot, ft_render_mode_normal);
570  }
571 
572  tex_x_size = bitmap.width;
573  tex_y_size = bitmap.rows;
574  tex_x_orig = slot->bitmap_left;
575  tex_y_orig = slot->bitmap_top;
576  alpha_mode = TransparencyAttrib::M_alpha;
577 
578  } else {
579  // Calculate suitable texture dimensions for the signed distance field.
580  // This is the same calculation that Freetype uses in its bitmap renderer.
581  FT_Outline_Get_CBox(&slot->outline, &bounds);
582 
583  bounds.xMin = bounds.xMin & ~63;
584  bounds.yMin = bounds.yMin & ~63;
585  bounds.xMax = (bounds.xMax + 63) & ~63;
586  bounds.yMax = (bounds.yMax + 63) & ~63;
587 
588  tex_x_size = (bounds.xMax - bounds.xMin) >> 6;
589  tex_y_size = (bounds.yMax - bounds.yMin) >> 6;
590  tex_x_orig = (bounds.xMin >> 6);
591  tex_y_orig = (bounds.yMax >> 6);
592  alpha_mode = TransparencyAttrib::M_binary;
593  }
594 
595  if (tex_x_size == 0 || tex_y_size == 0) {
596  // If we got an empty bitmap, it's a special case.
597 
598  PT(TextGlyph) glyph =
599  new DynamicTextGlyph(character, advance);
600  _empty_glyphs.push_back(glyph);
601  return glyph;
602 
603  } else {
604  DynamicTextGlyph *glyph;
605 
606  int outline = 0;
607 
608  if (_render_mode == RM_distance_field) {
609  tex_x_size /= _scale_factor;
610  tex_y_size /= _scale_factor;
611  int int_x_size = (int)ceil(tex_x_size);
612  int int_y_size = (int)ceil(tex_y_size);
613 
614  outline = 4;
615  int_x_size += outline * 2;
616  int_y_size += outline * 2;
617  tex_x_size += outline * 2;
618  tex_y_size += outline * 2;
619 
620  PNMImage image(int_x_size, int_y_size, PNMImage::CT_grayscale);
621  render_distance_field(image, outline, bounds.xMin, bounds.yMin);
622 
623  glyph = slot_glyph(character, int_x_size, int_y_size, advance);
624  if (!_needs_image_processing) {
625  copy_pnmimage_to_texture(image, glyph);
626  } else {
627  blend_pnmimage_to_texture(image, glyph, _fg);
628  }
629 
630  } else if (_tex_pixels_per_unit == _font_pixels_per_unit &&
631  !_needs_image_processing) {
632  // If the bitmap produced from the font doesn't require scaling or any
633  // other processing before it goes to the texture, we can just copy it
634  // directly into the texture.
635  glyph = slot_glyph(character, bitmap.width, bitmap.rows, advance);
636  copy_bitmap_to_texture(bitmap, glyph);
637 
638  } else {
639  // Otherwise, we need to copy to a PNMImage first, so we can scale it
640  // andor process it; and then copy it to the texture from there.
641  tex_x_size /= _scale_factor;
642  tex_y_size /= _scale_factor;
643  int int_x_size = (int)ceil(tex_x_size);
644  int int_y_size = (int)ceil(tex_y_size);
645  int bmp_x_size = (int)(int_x_size * _scale_factor + 0.5f);
646  int bmp_y_size = (int)(int_y_size * _scale_factor + 0.5f);
647 
648  PNMImage image(bmp_x_size, bmp_y_size, PNMImage::CT_grayscale);
649  copy_bitmap_to_pnmimage(bitmap, image);
650 
651  PNMImage reduced(int_x_size, int_y_size, PNMImage::CT_grayscale);
652  reduced.quick_filter_from(image);
653 
654  // convert the outline width from points to tex_pixels.
655  PN_stdfloat outline_pixels = _outline_width / _points_per_unit * _tex_pixels_per_unit;
656  outline = (int)ceil(outline_pixels);
657 
658  int_x_size += outline * 2;
659  int_y_size += outline * 2;
660  tex_x_size += outline * 2;
661  tex_y_size += outline * 2;
662  glyph = slot_glyph(character, int_x_size, int_y_size, advance);
663 
664  if (outline != 0) {
665  // Pad the glyph image to make room for the outline.
666  PNMImage padded(int_x_size, int_y_size, PNMImage::CT_grayscale);
667  padded.copy_sub_image(reduced, outline, outline);
668  copy_pnmimage_to_texture(padded, glyph);
669 
670  } else {
671  copy_pnmimage_to_texture(reduced, glyph);
672  }
673  }
674 
675  DynamicTextPage *page = glyph->get_page();
676  if (page != nullptr) {
677  int bitmap_top = (int)floor(tex_y_orig + outline * _scale_factor + 0.5f);
678  int bitmap_left = (int)floor(tex_x_orig - outline * _scale_factor + 0.5f);
679 
680  tex_x_size += glyph->_margin * 2;
681  tex_y_size += glyph->_margin * 2;
682 
683  // Determine the corners of the rectangle in geometric units.
684  PN_stdfloat tex_poly_margin = _poly_margin / _tex_pixels_per_unit;
685  PN_stdfloat origin_y = bitmap_top / _font_pixels_per_unit;
686  PN_stdfloat origin_x = bitmap_left / _font_pixels_per_unit;
687 
688  LVecBase4 dimensions(
689  origin_x - tex_poly_margin,
690  origin_y - tex_y_size / _tex_pixels_per_unit - tex_poly_margin,
691  origin_x + tex_x_size / _tex_pixels_per_unit + tex_poly_margin,
692  origin_y + tex_poly_margin);
693 
694  // And the corresponding corners in UV units. We add 0.5f to center the
695  // UV in the middle of its texel, to minimize roundoff errors when we
696  // are close to 1-to-1 pixel size.
697  LVecBase2i page_size = page->get_size();
698  LVecBase4 texcoords(
699  ((PN_stdfloat)(glyph->_x - _poly_margin) + 0.5f) / page_size[0],
700  1.0f - ((PN_stdfloat)(glyph->_y + _poly_margin + tex_y_size) + 0.5f) / page_size[1],
701  ((PN_stdfloat)(glyph->_x + _poly_margin + tex_x_size) + 0.5f) / page_size[0],
702  1.0f - ((PN_stdfloat)(glyph->_y - _poly_margin) + 0.5f) / page_size[1]);
703 
704  CPT(RenderState) state;
705  state = RenderState::make(TextureAttrib::make(page),
706  TransparencyAttrib::make(alpha_mode));
707  state = state->add_attrib(ColorAttrib::make_flat(LColor(1.0f, 1.0f, 1.0f, 1.0f)), -1);
708 
709  glyph->set_quad(dimensions, texcoords, state);
710  }
711 
712  return glyph;
713  }
714 }
715 
716 /**
717  * Copies a bitmap as rendered by FreeType directly into the texture memory
718  * image for the indicated glyph, without any scaling of pixels.
719  */
720 void DynamicTextFont::
721 copy_bitmap_to_texture(const FT_Bitmap &bitmap, DynamicTextGlyph *glyph) {
722  if (bitmap.pixel_mode == ft_pixel_mode_grays && bitmap.num_grays == 256) {
723  // This is the easy case: we can memcpy the rendered glyph directly into
724  // our texture image, one row at a time.
725  unsigned char *buffer_row = bitmap.buffer;
726  for (int yi = 0; yi < (int)bitmap.rows; yi++) {
727 
728  unsigned char *texture_row = glyph->get_row(yi);
729  nassertv(texture_row != nullptr);
730  memcpy(texture_row, buffer_row, bitmap.width);
731  buffer_row += bitmap.pitch;
732  }
733 
734  } else if (bitmap.pixel_mode == ft_pixel_mode_mono) {
735  // This is a little bit more work: we have to expand the one-bit-per-pixel
736  // bitmap into a one-byte-per-pixel texture.
737  unsigned char *buffer_row = bitmap.buffer;
738  for (int yi = 0; yi < (int)bitmap.rows; yi++) {
739  unsigned char *texture_row = glyph->get_row(yi);
740  nassertv(texture_row != nullptr);
741 
742  int bit = 0x80;
743  unsigned char *b = buffer_row;
744  for (int xi = 0; xi < (int)bitmap.width; xi++) {
745  if (*b & bit) {
746  texture_row[xi] = 0xff;
747  } else {
748  texture_row[xi] = 0x00;
749  }
750  bit >>= 1;
751  if (bit == 0) {
752  ++b;
753  bit = 0x80;
754  }
755  }
756 
757  buffer_row += bitmap.pitch;
758  }
759 
760 
761  } else if (bitmap.pixel_mode == ft_pixel_mode_grays) {
762  // Here we must expand a grayscale pixmap with n levels of gray into our
763  // 256-level texture.
764  unsigned char *buffer_row = bitmap.buffer;
765  for (int yi = 0; yi < (int)bitmap.rows; yi++) {
766  unsigned char *texture_row = glyph->get_row(yi);
767  nassertv(texture_row != nullptr);
768  for (int xi = 0; xi < (int)bitmap.width; xi++) {
769  texture_row[xi] = (int)(buffer_row[xi] * 255) / (bitmap.num_grays - 1);
770  }
771  buffer_row += bitmap.pitch;
772  }
773 
774  } else {
775  text_cat.error()
776  << "Unexpected pixel mode in bitmap: " << (int)bitmap.pixel_mode << "\n";
777  }
778 }
779 
780 /**
781  * Copies a bitmap stored in a PNMImage into the texture memory image for the
782  * indicated glyph.
783  */
784 void DynamicTextFont::
785 copy_pnmimage_to_texture(const PNMImage &image, DynamicTextGlyph *glyph) {
786  if (!_needs_image_processing) {
787  // Copy the image directly into the alpha component of the texture.
788  nassertv(glyph->_page->get_num_components() == 1);
789  for (int yi = 0; yi < image.get_y_size(); yi++) {
790  unsigned char *texture_row = glyph->get_row(yi);
791  nassertv(texture_row != nullptr);
792  for (int xi = 0; xi < image.get_x_size(); xi++) {
793  texture_row[xi] = image.get_gray_val(xi, yi);
794  }
795  }
796 
797  } else {
798  if (_has_outline) {
799  // Gaussian blur the glyph to generate an outline.
800  PNMImage outline(image.get_x_size(), image.get_y_size(), PNMImage::CT_grayscale);
801  PN_stdfloat outline_pixels = _outline_width / _points_per_unit * _tex_pixels_per_unit;
802  outline.gaussian_filter_from(outline_pixels * 0.707, image);
803 
804  // Filter the resulting outline to make a harder edge. Square
805  // _outline_feather first to make the range more visually linear (this
806  // approximately compensates for the Gaussian falloff of the feathered
807  // edge).
808  PN_stdfloat f = _outline_feather * _outline_feather;
809 
810  for (int yi = 0; yi < outline.get_y_size(); yi++) {
811  for (int xi = 0; xi < outline.get_x_size(); xi++) {
812  PN_stdfloat v = outline.get_gray(xi, yi);
813  if (v == 0.0f) {
814  // Do nothing.
815  } else if (v >= f) {
816  // Clamp to 1.
817  outline.set_gray(xi, yi, 1.0);
818  } else {
819  // Linearly scale the range 0 .. f onto 0 .. 1.
820  outline.set_gray(xi, yi, v / f);
821  }
822  }
823  }
824 
825  // Now blend that into the texture.
826  blend_pnmimage_to_texture(outline, glyph, _outline_color);
827  }
828 
829  // Colorize the image as we copy it in. This assumes the previous color
830  // at this part of the texture was already initialized to the background
831  // color.
832  blend_pnmimage_to_texture(image, glyph, _fg);
833  }
834 }
835 
836 /**
837  * Blends the PNMImage into the appropriate part of the texture, where 0.0 in
838  * the image indicates the color remains the same, and 1.0 indicates the color
839  * is assigned the indicated foreground color.
840  */
841 void DynamicTextFont::
842 blend_pnmimage_to_texture(const PNMImage &image, DynamicTextGlyph *glyph,
843  const LColor &fg) {
844  LColor fgv = fg * 255.0f;
845 
846  int num_components = glyph->_page->get_num_components();
847  if (num_components == 1) {
848  // Luminance or alpha.
849  int ci = 3;
850  if (glyph->_page->get_format() != Texture::F_alpha) {
851  ci = 0;
852  }
853 
854  for (int yi = 0; yi < image.get_y_size(); yi++) {
855  unsigned char *texture_row = glyph->get_row(yi);
856  nassertv(texture_row != nullptr);
857  for (int xi = 0; xi < image.get_x_size(); xi++) {
858  unsigned char *tr = texture_row + xi;
859  PN_stdfloat t = (PN_stdfloat)image.get_gray(xi, yi);
860  tr[0] = (unsigned char)(tr[0] + t * (fgv[ci] - tr[0]));
861  }
862  }
863 
864  } else if (num_components == 2) {
865  // Luminance + alpha.
866 
867  for (int yi = 0; yi < image.get_y_size(); yi++) {
868  unsigned char *texture_row = glyph->get_row(yi);
869  nassertv(texture_row != nullptr);
870  for (int xi = 0; xi < image.get_x_size(); xi++) {
871  unsigned char *tr = texture_row + xi * 2;
872  PN_stdfloat t = (PN_stdfloat)image.get_gray(xi, yi);
873  tr[0] = (unsigned char)(tr[0] + t * (fgv[0] - tr[0]));
874  tr[1] = (unsigned char)(tr[1] + t * (fgv[3] - tr[1]));
875  }
876  }
877 
878  } else if (num_components == 3) {
879  // RGB.
880 
881  for (int yi = 0; yi < image.get_y_size(); yi++) {
882  unsigned char *texture_row = glyph->get_row(yi);
883  nassertv(texture_row != nullptr);
884  for (int xi = 0; xi < image.get_x_size(); xi++) {
885  unsigned char *tr = texture_row + xi * 3;
886  PN_stdfloat t = (PN_stdfloat)image.get_gray(xi, yi);
887  tr[0] = (unsigned char)(tr[0] + t * (fgv[2] - tr[0]));
888  tr[1] = (unsigned char)(tr[1] + t * (fgv[1] - tr[1]));
889  tr[2] = (unsigned char)(tr[2] + t * (fgv[0] - tr[2]));
890  }
891  }
892 
893  } else { // (num_components == 4)
894  // RGBA.
895 
896  for (int yi = 0; yi < image.get_y_size(); yi++) {
897  unsigned char *texture_row = glyph->get_row(yi);
898  nassertv(texture_row != nullptr);
899  for (int xi = 0; xi < image.get_x_size(); xi++) {
900  unsigned char *tr = texture_row + xi * 4;
901  PN_stdfloat t = (PN_stdfloat)image.get_gray(xi, yi);
902  tr[0] = (unsigned char)(tr[0] + t * (fgv[2] - tr[0]));
903  tr[1] = (unsigned char)(tr[1] + t * (fgv[1] - tr[1]));
904  tr[2] = (unsigned char)(tr[2] + t * (fgv[0] - tr[2]));
905  tr[3] = (unsigned char)(tr[3] + t * (fgv[3] - tr[3]));
906  }
907  }
908  }
909 }
910 
911 /**
912  * Chooses a page that will have room for a glyph of the indicated size (after
913  * expanding the indicated size by the current margin). Returns the newly-
914  * allocated glyph on the chosen page; the glyph has not been filled in yet
915  * except with its size.
916  */
917 DynamicTextGlyph *DynamicTextFont::
918 slot_glyph(int character, int x_size, int y_size, PN_stdfloat advance) {
919  // Increase the indicated size by the current margin.
920  x_size += _texture_margin * 2;
921  y_size += _texture_margin * 2;
922 
923  if (!_pages.empty()) {
924  // Start searching on the preferred page. That way, we'll fill up the
925  // preferred page first, and we can gradually rotate this page around; it
926  // keeps us from spending too much time checking already-filled pages for
927  // space.
928  _preferred_page = _preferred_page % _pages.size();
929  int pi = _preferred_page;
930 
931  do {
932  DynamicTextPage *page = _pages[pi];
933  DynamicTextGlyph *glyph = page->slot_glyph(character, x_size, y_size, _texture_margin, advance);
934  if (glyph != nullptr) {
935  // Once we found a page to hold the glyph, that becomes our new
936  // preferred page.
937  _preferred_page = pi;
938  return glyph;
939  }
940 
941  if (page->is_empty()) {
942  // If we couldn't even put it on an empty page, we're screwed.
943  text_cat.error()
944  << "Glyph of size " << x_size << " by " << y_size
945  << " pixels won't fit on an empty page.\n";
946  return nullptr;
947  }
948 
949  pi = (pi + 1) % _pages.size();
950  } while (pi != _preferred_page);
951  }
952 
953  // All pages are filled. Can we free up space by removing some old glyphs?
954  if (garbage_collect() != 0) {
955  // Yes, we just freed up some space. Try once more, recursively.
956  return slot_glyph(character, x_size, y_size, advance);
957 
958  } else {
959  // No good; all recorded glyphs are actually in use. We need to make a
960  // new page.
961  _preferred_page = _pages.size();
962  PT(DynamicTextPage) page = new DynamicTextPage(this, _preferred_page);
963  _pages.push_back(page);
964  return page->slot_glyph(character, x_size, y_size, _texture_margin, advance);
965  }
966 }
967 
968 /**
969  * Converts from the _contours list to an actual glyph geometry, as a
970  * wireframe render.
971  */
972 void DynamicTextFont::
973 render_wireframe_contours(TextGlyph *glyph) {
974  PT(GeomVertexData) vdata = new GeomVertexData
975  (std::string(), GeomVertexFormat::get_v3(),
976  Geom::UH_static);
977  GeomVertexWriter vertex(vdata, InternalName::get_vertex());
978 
979  PT(GeomLinestrips) lines = new GeomLinestrips(Geom::UH_static);
980 
981  Contours::const_iterator ci;
982  for (ci = _contours.begin(); ci != _contours.end(); ++ci) {
983  const Contour &contour = (*ci);
984  Points::const_iterator pi;
985 
986  for (pi = contour._points.begin(); pi != contour._points.end(); ++pi) {
987  const LPoint2 &p = (*pi)._p;
988  vertex.add_data3(p[0], 0.0f, p[1]);
989  }
990 
991  lines->add_next_vertices(contour._points.size());
992  lines->close_primitive();
993  }
994 
995  glyph->set_geom(vdata, lines, RenderState::make_empty());
996  _contours.clear();
997 }
998 
999 /**
1000  * Converts from the _contours list to an actual glyph geometry, as a polygon
1001  * render.
1002  */
1003 void DynamicTextFont::
1004 render_polygon_contours(TextGlyph *glyph, bool face, bool extrude) {
1005  PT(GeomVertexData) vdata = new GeomVertexData
1006  (std::string(), GeomVertexFormat::get_v3n3(),
1007  Geom::UH_static);
1008  GeomVertexWriter vertex(vdata, InternalName::get_vertex());
1009  GeomVertexWriter normal(vdata, InternalName::get_normal());
1010 
1011  PT(GeomTriangles) tris = new GeomTriangles(Geom::UH_static);
1012  Triangulator t;
1013 
1014  Contours::iterator ci;
1015 
1016  if (face) {
1017  // First, build up the list of vertices for the face, and determine which
1018  // contours are solid and which are holes.
1019  for (ci = _contours.begin(); ci != _contours.end(); ++ci) {
1020  Contour &contour = (*ci);
1021 
1022  t.clear_polygon();
1023  contour._start_vertex = t.get_num_vertices();
1024  for (size_t i = 0; i < contour._points.size() - 1; ++i) {
1025  const LPoint2 &p = contour._points[i]._p;
1026  vertex.add_data3(p[0], 0.0f, p[1]);
1027  normal.add_data3(0.0f, -1.0f, 0.0f);
1028  int vi = t.add_vertex(p[0], p[1]);
1029  t.add_polygon_vertex(vi);
1030  }
1031 
1032  contour._is_solid = t.is_left_winding();
1033  }
1034 
1035  // Now go back and generate the actual triangles for the face.
1036  for (ci = _contours.begin(); ci != _contours.end(); ++ci) {
1037  const Contour &contour = (*ci);
1038 
1039  if (contour._is_solid && !contour._points.empty()) {
1040  t.clear_polygon();
1041  for (size_t i = 0; i < contour._points.size() - 1; ++i) {
1042  t.add_polygon_vertex(contour._start_vertex + i);
1043  }
1044 
1045  // Also add all the holes to each polygon.
1046  Contours::iterator cj;
1047  for (cj = _contours.begin(); cj != _contours.end(); ++cj) {
1048  Contour &hole = (*cj);
1049  if (!hole._is_solid && !hole._points.empty()) {
1050  t.begin_hole();
1051  for (size_t j = 0; j < hole._points.size() - 1; ++j) {
1052  t.add_hole_vertex(hole._start_vertex + j);
1053  }
1054  }
1055  }
1056 
1057  t.triangulate();
1058  int num_triangles = t.get_num_triangles();
1059  for (int ti = 0; ti < num_triangles; ++ti) {
1060  tris->add_vertex(t.get_triangle_v0(ti));
1061  tris->add_vertex(t.get_triangle_v1(ti));
1062  tris->add_vertex(t.get_triangle_v2(ti));
1063  tris->close_primitive();
1064  }
1065  }
1066  }
1067  }
1068 
1069  if (extrude) {
1070  // If we're generating extruded geometry (polygons along the edges, down
1071  // the y axis), generate them now. These are pretty easy, but we need to
1072  // create more vertices--they don't share the same normals.
1073  for (ci = _contours.begin(); ci != _contours.end(); ++ci) {
1074  const Contour &contour = (*ci);
1075 
1076  for (size_t i = 0; i < contour._points.size(); ++i) {
1077  const ContourPoint &cp = contour._points[i];
1078  const LPoint2 &p = cp._p;
1079  const LVector2 &t_in = cp._in;
1080  const LVector2 &t_out = cp._out;
1081 
1082  LVector3 n_in(t_in[1], 0.0f, -t_in[0]);
1083  vertex.add_data3(p[0], 1.0f, p[1]);
1084  vertex.add_data3(p[0], 0.0f, p[1]);
1085  normal.add_data3(n_in);
1086  normal.add_data3(n_in);
1087 
1088  if (i != 0) {
1089  int vi = vertex.get_write_row();
1090  tris->add_vertex(vi - 4);
1091  tris->add_vertex(vi - 2);
1092  tris->add_vertex(vi - 1);
1093  tris->close_primitive();
1094  tris->add_vertex(vi - 1);
1095  tris->add_vertex(vi - 3);
1096  tris->add_vertex(vi - 4);
1097  tris->close_primitive();
1098  }
1099 
1100  if (i != contour._points.size() - 1 && !t_in.almost_equal(t_out)) {
1101  // If the out tangent is different from the in tangent, we need to
1102  // store new vertices for the next quad.
1103  LVector3 n_out(t_out[1], 0.0f, -t_out[0]);
1104  vertex.add_data3(p[0], 1.0f, p[1]);
1105  vertex.add_data3(p[0], 0.0f, p[1]);
1106  normal.add_data3(n_out);
1107  normal.add_data3(n_out);
1108  }
1109  }
1110  }
1111 
1112  if (face) {
1113  // Render the back side of the face too.
1114  int back_start = vertex.get_write_row();
1115 
1116  for (ci = _contours.begin(); ci != _contours.end(); ++ci) {
1117  Contour &contour = (*ci);
1118  for (size_t i = 0; i < contour._points.size() - 1; ++i) {
1119  const LPoint2 &p = contour._points[i]._p;
1120  vertex.add_data3(p[0], 1.0f, p[1]);
1121  normal.add_data3(0.0f, 1.0f, 0.0f);
1122  }
1123  }
1124 
1125  // Now go back and generate the actual triangles for the face.
1126  for (ci = _contours.begin(); ci != _contours.end(); ++ci) {
1127  const Contour &contour = (*ci);
1128 
1129  if (contour._is_solid && !contour._points.empty()) {
1130  t.clear_polygon();
1131  for (size_t i = 0; i < contour._points.size() - 1; ++i) {
1132  t.add_polygon_vertex(contour._start_vertex + i);
1133  }
1134 
1135  // Also add all the holes to each polygon.
1136  Contours::iterator cj;
1137  for (cj = _contours.begin(); cj != _contours.end(); ++cj) {
1138  Contour &hole = (*cj);
1139  if (!hole._is_solid && !hole._points.empty()) {
1140  t.begin_hole();
1141  for (size_t j = 0; j < hole._points.size() - 1; ++j) {
1142  t.add_hole_vertex(hole._start_vertex + j);
1143  }
1144  }
1145  }
1146 
1147  t.triangulate();
1148  int num_triangles = t.get_num_triangles();
1149  for (int ti = 0; ti < num_triangles; ++ti) {
1150  tris->add_vertex(t.get_triangle_v2(ti) + back_start);
1151  tris->add_vertex(t.get_triangle_v1(ti) + back_start);
1152  tris->add_vertex(t.get_triangle_v0(ti) + back_start);
1153  tris->close_primitive();
1154  }
1155  }
1156  }
1157  }
1158  }
1159 
1160  glyph->set_geom(vdata, tris, RenderState::make_empty());
1161  // glyph->set_geom(vdata, tris, RenderState::make(RenderModeAttrib::make(Ren
1162  // derModeAttrib::M_wireframe))); glyph->set_geom(vdata, tris,
1163  // RenderState::make(AntialiasAttrib::make(AntialiasAttrib::M_auto)));
1164  _contours.clear();
1165 }
1166 
1167 #endif // HAVE_FREETYPE
get_ref_count
Returns the current reference count.
This object provides a high-level interface for quickly writing a sequence of numeric values from a v...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
The name of this class derives from the fact that we originally implemented it as a layer on top of t...
Definition: pnmImage.h:58
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
xelval get_gray_val(int x, int y) const
Returns the gray component color at the indicated pixel.
Definition: pnmImage.I:395
void triangulate()
Does the work of triangulating the specified polygon.
float get_gray(int x, int y) const
Returns the gray component color at the indicated pixel.
Definition: pnmImage.I:785
int get_triangle_v0(int n) const
Returns vertex 0 of the nth triangle generated by the previous call to triangulate().
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void begin_hole()
Finishes the previous hole, if any, and prepares to add a new hole.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
int get_triangle_v2(int n) const
Returns vertex 2 of the nth triangle generated by the previous call to triangulate().
void set_geom(GeomVertexData *vdata, GeomPrimitive *prim, const RenderState *state)
Sets the geom from a pre-built Geom object.
Definition: textGlyph.cxx:119
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void add_hole_vertex(int index)
Adds the next consecutive vertex of the current hole.
int get_y_size() const
Returns the number of pixels in the Y direction.
int get_x_size() const
Returns the number of pixels in the X direction.
An encapsulation of a font; i.e.
Definition: textFont.h:32
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void clear_polygon()
Removes the current polygon definition (and its set of holes), but does not clear the vertex pool.
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
std::ostream & indent(std::ostream &out, int indent_level)
A handy function for doing text formatting.
Definition: indent.cxx:20
This class can triangulate a convex or concave polygon, even one with holes.
Definition: triangulator.h:32
This defines the actual numeric vertex data stored in a Geom, in the structure defined by a particula...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A representation of a single glyph (character) from a font.
Definition: textGlyph.h:28
void gaussian_filter_from(float radius, const PNMImage &copy)
Makes a resized copy of the indicated image into this one using the indicated filter.
bool is_left_winding() const
Returns true if the polygon vertices are listed in counterclockwise order, or false if they appear to...
Definition: triangulator.I:47
int get_num_triangles() const
Returns the number of triangles generated by the previous call to triangulate().
This represents a unique collection of RenderAttrib objects that correspond to a particular renderabl...
Definition: renderState.h:47
get_num_vertices
Returns the number of vertices in the pool.
Definition: triangulator.h:42
void add_polygon_vertex(int index)
Adds the next consecutive vertex of the polygon.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
static const GeomVertexFormat * get_v3n3()
Returns a standard vertex format with a 3-component normal and a 3-component vertex position.
Defines a series of line strips.
Defines a series of disconnected triangles.
Definition: geomTriangles.h:23
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
int get_triangle_v1(int n) const
Returns vertex 1 of the nth triangle generated by the previous call to triangulate().
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
int add_vertex(const LPoint2d &point)
Adds a new vertex to the vertex pool.
static const GeomVertexFormat * get_v3()
Returns a standard vertex format with just a 3-component vertex position.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.