Panda3D
 All Classes Functions Variables Enumerations
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
int get_triangle_v0(int n) const
Returns vertex 0 of the nth triangle generated by the previous call to triangulate().
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
This object provides a high-level interface for quickly writing a sequence of numeric values from a v...
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_vertices() const
Returns the number of vertices in the pool.
Definition: triangulator.I:37
void reset(int num_vertices)
Resets all the vertices and knots to their default values, and sets the curve up with the indicated n...
void triangulate()
Does the work of triangulating the specified polygon.
This class is an abstraction for evaluating NURBS curves.
float get_gray(int x, int y) const
Returns the gray component color at the indicated pixel.
Definition: pnmImage.I:928
bool normalize()
Normalizes the vector in place.
Definition: lvecBase2.h:680
int get_triangle_v1(int n) const
Returns vertex 1 of the nth triangle generated by the previous call to triangulate().
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_x_size() const
Returns the number of pixels in the X direction.
int get_triangle_v2(int n) const
Returns vertex 2 of the nth triangle generated by the previous call to triangulate().
int get_y_size() const
Returns the number of pixels in the Y direction.
int get_num_triangles() const
Returns the number of triangles generated by the previous call to triangulate().
void add_hole_vertex(int index)
Adds the next consecutive vertex of the current hole.
An encapsulation of a font; i.e.
Definition: textFont.h:36
void clear_polygon()
Removes the current polygon definition (and its set of holes), but does not clear the vertex pool...
bool almost_equal(const LVecBase2f &other, float threshold) const
Returns true if two vectors are memberwise equal within a specified tolerance.
Definition: lvecBase2.h:1062
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 container for geometry primitives.
Definition: geom.h:58
A representation of a single glyph (character) from a font.
Definition: textGlyph.h:31
void gaussian_filter_from(float radius, const PNMImage &copy)
Makes a resized copy of the indicated image into this one using the indicated filter.
static const LVector2f & zero()
Returns a zero-length vector.
Definition: lvector2.h:218
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
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 add_vertex(const LPoint2d &point)
Adds a new vertex to the vertex pool.
bool normalize()
Normalizes the vector in place.
Definition: lvecBase3.h:782
The result of a NurbsCurveEvaluator.
xelval get_gray_val(int x, int y) const
Returns the gray component color at the indicated pixel.
Definition: pnmImage.I:490
int get_num_components() const
Returns the number of elements in the vector, four.
Definition: lvecBase4.h:755