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