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
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
Defines a series of line strips.
Defines a series of disconnected triangles.
Definition: geomTriangles.h:23
This defines the actual numeric vertex data stored in a Geom, in the structure defined by a particula...
static const GeomVertexFormat * get_v3()
Returns a standard vertex format with just a 3-component vertex position.
static const GeomVertexFormat * get_v3n3()
Returns a standard vertex format with a 3-component normal and a 3-component vertex position.
This object provides a high-level interface for quickly writing a sequence of numeric values from a v...
int get_x_size() const
Returns the number of pixels in the X direction.
int get_y_size() const
Returns the number of pixels in the Y direction.
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
float get_gray(int x, int y) const
Returns the gray component color at the indicated pixel.
Definition: pnmImage.I:799
xelval get_gray_val(int x, int y) const
Returns the gray component color at the indicated pixel.
Definition: pnmImage.I:484
get_ref_count
Returns the current reference count.
This represents a unique collection of RenderAttrib objects that correspond to a particular renderabl...
Definition: renderState.h:47
An encapsulation of a font; i.e.
Definition: textFont.h:32
A representation of a single glyph (character) from a font.
Definition: textGlyph.h:28
void set_geom(GeomVertexData *vdata, GeomPrimitive *prim, const RenderState *state)
Sets the geom from a pre-built Geom object.
Definition: textGlyph.cxx:119
This class can triangulate a convex or concave polygon, even one with holes.
Definition: triangulator.h:32
int get_triangle_v0(int n) const
Returns vertex 0 of the nth triangle generated by the previous call to triangulate().
void add_polygon_vertex(int index)
Adds the next consecutive vertex of the polygon.
void clear_polygon()
Removes the current polygon definition (and its set of holes), but does not clear the vertex pool.
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_triangle_v1(int n) const
Returns vertex 1 of the nth triangle generated by the previous call to triangulate().
int get_num_triangles() const
Returns the number of triangles generated by the previous call to triangulate().
int add_vertex(const LPoint2d &point)
Adds a new vertex to the vertex pool.
void add_hole_vertex(int index)
Adds the next consecutive vertex of the current hole.
void triangulate()
Does the work of triangulating the specified polygon.
void begin_hole()
Finishes the previous hole, if any, and prepares to add a new hole.
get_num_vertices
Returns the number of vertices in the pool.
Definition: triangulator.h:42
int get_triangle_v2(int n) const
Returns vertex 2 of the nth triangle generated by the previous call to triangulate().
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
std::ostream & indent(std::ostream &out, int indent_level)
A handy function for doing text formatting.
Definition: indent.cxx:20
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.