Panda3D
freetypeFont.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file freetypeFont.cxx
10  * @author drose
11  * @date 2003-09-07
12  */
13 
14 #include "freetypeFont.h"
15 
16 #ifdef HAVE_FREETYPE
17 
18 #include "config_pnmtext.h"
19 #include "config_putil.h"
20 #include "config_express.h"
21 #include "virtualFileSystem.h"
22 #include "nurbsCurveEvaluator.h"
23 #include "nurbsCurveResult.h"
24 
25 #undef interface // I don't know where this symbol is defined, but it interferes with FreeType.
26 #include FT_OUTLINE_H
27 
28 using std::istream;
29 using std::ostream;
30 using std::string;
31 
32 // This constant determines how big a particular point size font appears to
33 // be. By convention, 10 points is 1 unit (e.g. 1 foot) high.
34 const PN_stdfloat FreetypeFont::_points_per_unit = 10.0f;
35 
36 // A universal typographic convention.
37 const PN_stdfloat FreetypeFont::_points_per_inch = 72.0f;
38 
39 /**
40  *
41  */
42 FreetypeFont::
43 FreetypeFont() {
44  _face = nullptr;
45 
46  _point_size = text_point_size;
47  _requested_pixels_per_unit = text_pixels_per_unit;
48  _tex_pixels_per_unit = text_pixels_per_unit;
49  _requested_scale_factor = text_scale_factor;
50  _scale_factor = text_scale_factor;
51  _native_antialias = text_native_antialias;
52  _winding_order = WO_default;
53 
54  _line_height = 1.0f;
55  _space_advance = 0.25f;
56 
57  _char_size = 0;
58  _dpi = 0;
59  _pixel_width = 0;
60  _pixel_height = 0;
61 }
62 
63 /**
64  *
65  */
66 FreetypeFont::
67 FreetypeFont(const FreetypeFont &copy) :
68  Namable(copy),
69  _point_size(copy._point_size),
70  _requested_pixels_per_unit(copy._requested_pixels_per_unit),
71  _tex_pixels_per_unit(copy._tex_pixels_per_unit),
72  _requested_scale_factor(copy._requested_scale_factor),
73  _scale_factor(copy._scale_factor),
74  _native_antialias(copy._native_antialias),
75  _font_pixels_per_unit(copy._font_pixels_per_unit),
76  _winding_order(copy._winding_order),
77  _line_height(copy._line_height),
78  _space_advance(copy._space_advance),
79  _face(copy._face),
80  _char_size(copy._char_size),
81  _dpi(copy._dpi),
82  _pixel_width(copy._pixel_width),
83  _pixel_height(copy._pixel_height)
84 {
85 }
86 
87 /**
88  * This method accepts the name of some font file that FreeType can read,
89  * along with face_index, indicating which font within the file to load
90  * (usually 0).
91  */
92 bool FreetypeFont::
93 load_font(const Filename &font_filename, int face_index) {
94  unload_font();
95  _face = new FreetypeFace;
96  if (!_face->_ft_ok) {
97  pnmtext_cat.error()
98  << "Unable to read font " << font_filename
99  << ": FreeType library not initialized properly.\n";
100  unload_font();
101  return false;
102  }
103 
104  bool exists = false;
105  int error;
106  Filename path(font_filename);
108  vfs->resolve_filename(path, get_model_path());
109  exists = vfs->read_file(path, _face->_font_data, true);
110  if (exists) {
111  FT_Face face;
112  error = FT_New_Memory_Face(_face->_ft_library,
113  (const FT_Byte *)_face->_font_data.data(),
114  _face->_font_data.length(),
115  face_index, &face);
116  _face->set_face(face);
117  }
118 
119  bool okflag = false;
120  if (!exists) {
121  pnmtext_cat.error()
122  << "Unable to find font file " << font_filename << "\n";
123  } else {
124  if (error == FT_Err_Unknown_File_Format) {
125  pnmtext_cat.error()
126  << "Unable to read font " << font_filename << ": unknown file format.\n";
127  } else if (error) {
128  pnmtext_cat.error()
129  << "Unable to read font " << font_filename << ": invalid.\n";
130 
131  } else {
132  okflag = reset_scale();
133  }
134  }
135 
136  if (!okflag) {
137  unload_font();
138  }
139 
140  return okflag;
141 }
142 
143 /**
144  * This method accepts a table of data representing the font file, loaded from
145  * some source other than a filename on disk.
146  */
147 bool FreetypeFont::
148 load_font(const char *font_data, int data_length, int face_index) {
149  unload_font();
150  _face = new FreetypeFace;
151 
152  if (!_face->_ft_ok) {
153  pnmtext_cat.error()
154  << "Unable to read font: FreeType library not initialized properly.\n";
155  unload_font();
156  return false;
157  }
158 
159  int error;
160  FT_Face face;
161  error = FT_New_Memory_Face(_face->_ft_library,
162  (const FT_Byte *)font_data, data_length,
163  face_index, &face);
164  _face->set_face(face);
165 
166  bool okflag = false;
167  if (error == FT_Err_Unknown_File_Format) {
168  pnmtext_cat.error()
169  << "Unable to read font: unknown file format.\n";
170  } else if (error) {
171  pnmtext_cat.error()
172  << "Unable to read font: invalid.\n";
173 
174  } else {
175  okflag = reset_scale();
176  }
177 
178  if (!okflag) {
179  unload_font();
180  }
181 
182  return okflag;
183 }
184 
185 /**
186  *
187  */
188 void FreetypeFont::
189 unload_font() {
190  _face = nullptr;
191 }
192 
193 /**
194  * Returns the WindingOrder value associated with the given string
195  * representation, or WO_invalid if the string does not match any known
196  * WindingOrder value.
197  */
198 FreetypeFont::WindingOrder FreetypeFont::
199 string_winding_order(const string &string) {
200  if (cmp_nocase_uh(string, "default") == 0) {
201  return WO_default;
202  } else if (cmp_nocase_uh(string, "left") == 0) {
203  return WO_left;
204  } else if (cmp_nocase_uh(string, "right") == 0) {
205  return WO_right;
206  } else {
207  return WO_invalid;
208  }
209 }
210 
211 /**
212  * Invokes Freetype to load and render the indicated glyph into a bitmap.
213  * Returns true if successful, false otherwise.
214  */
215 bool FreetypeFont::
216 load_glyph(FT_Face face, int glyph_index, bool prerender) {
217  int flags = FT_LOAD_RENDER;
218  if (!_native_antialias) {
219  flags |= FT_LOAD_MONOCHROME;
220  }
221 
222  if (!prerender) {
223  // If we want to render as an outline font, don't pre-render it to a
224  // bitmap.
225  flags = 0;
226  }
227 
228  int error = FT_Load_Glyph(face, glyph_index, flags);
229  if (error) {
230  pnmtext_cat.error()
231  << "Unable to render glyph " << glyph_index << "\n";
232  return false;
233  }
234  return true;
235 }
236 
237 /**
238  * Copies a bitmap as rendered by FreeType into a PNMImage, so it can be
239  * rescaled.
240  */
241 void FreetypeFont::
242 copy_bitmap_to_pnmimage(const FT_Bitmap &bitmap, PNMImage &image) {
243  if (bitmap.pixel_mode == ft_pixel_mode_grays &&
244  bitmap.num_grays == (int)image.get_maxval() + 1) {
245  // This is the easy case: we can copy the rendered glyph directly into our
246  // image, one pixel at a time.
247  unsigned char *buffer_row = bitmap.buffer;
248  for (int yi = 0; yi < (int)bitmap.rows; yi++) {
249  for (int xi = 0; xi < (int)bitmap.width; xi++) {
250  image.set_gray_val(xi, yi, buffer_row[xi]);
251  }
252  buffer_row += bitmap.pitch;
253  }
254 
255  } else if (bitmap.pixel_mode == ft_pixel_mode_mono) {
256  // This is a little bit more work: we have to expand the one-bit-per-pixel
257  // bitmap into a one-byte-per-pixel image.
258  unsigned char *buffer_row = bitmap.buffer;
259  for (int yi = 0; yi < (int)bitmap.rows; yi++) {
260  xelval maxval = image.get_maxval();
261  int bit = 0x80;
262  unsigned char *b = buffer_row;
263  for (int xi = 0; xi < (int)bitmap.width; xi++) {
264  if (*b & bit) {
265  image.set_gray_val(xi, yi, maxval);
266  } else {
267  image.set_gray_val(xi, yi, 0);
268  }
269  bit >>= 1;
270  if (bit == 0) {
271  ++b;
272  bit = 0x80;
273  }
274  }
275 
276  buffer_row += bitmap.pitch;
277  }
278 
279 
280  } else if (bitmap.pixel_mode == ft_pixel_mode_grays) {
281  // Here we must expand a grayscale pixmap with n levels of gray into our
282  // 256-level texture.
283  unsigned char *buffer_row = bitmap.buffer;
284  for (int yi = 0; yi < (int)bitmap.rows; yi++) {
285  for (int xi = 0; xi < (int)bitmap.width; xi++) {
286  image.set_gray(xi, yi, (PN_stdfloat)buffer_row[xi] / (bitmap.num_grays - 1));
287  }
288  buffer_row += bitmap.pitch;
289  }
290 
291  } else {
292  pnmtext_cat.error()
293  << "Unexpected pixel mode in bitmap: " << (int)bitmap.pixel_mode << "\n";
294  }
295 }
296 
297 /**
298  * Resets the font based on the current values for _point_size,
299  * _tex_pixels_per_unit, and _scale_factor. Returns true if successful, false
300  * otherwise.
301  */
302 bool FreetypeFont::
303 reset_scale() {
304  if (_face == nullptr) {
305  return false;
306  }
307 
308  // Get the face, without requesting a particular size yet (we'll figure out
309  // the size in a second).
310  FT_Face face = _face->acquire_face(0, 0, 0, 0);
311 
312  // The font may be rendered larger (by a factor of _scale_factor), and then
313  // reduced into the texture. Hence the difference between
314  // _font_pixels_per_unit and _tex_pixels_per_unit.
315  _tex_pixels_per_unit = _requested_pixels_per_unit;
316  _scale_factor = _requested_scale_factor;
317  _font_pixels_per_unit = _tex_pixels_per_unit * _scale_factor;
318 
319  _pixel_height = 0;
320  _pixel_width = 0;
321  PN_stdfloat units_per_inch = (_points_per_inch / _points_per_unit);
322  _dpi = (int)(_font_pixels_per_unit * units_per_inch);
323  _char_size = (int)(_point_size * 64);
324 
325  int error = FT_Set_Char_Size(face, _char_size, _char_size, _dpi, _dpi);
326  if (error) {
327  // If we were unable to set a particular char size, perhaps we have a non-
328  // scalable font. Try to figure out the next larger available size, or
329  // the largest size available if nothing is larger.
330  int desired_height = (int)(_font_pixels_per_unit * _point_size / _points_per_unit + 0.5f);
331  int best_size = -1;
332  int largest_size = -1;
333  if (face->num_fixed_sizes > 0) {
334  largest_size = 0;
335  int best_diff = 0;
336  for (int i = 0; i < face->num_fixed_sizes; i++) {
337  int diff = face->available_sizes[i].height - desired_height;
338  if (diff > 0 && (best_size == -1 || diff < best_diff)) {
339  best_size = i;
340  best_diff = diff;
341  }
342  if (face->available_sizes[i].height > face->available_sizes[largest_size].height) {
343  largest_size = i;
344  }
345  }
346  }
347  if (best_size < 0) {
348  best_size = largest_size;
349  }
350 
351  if (best_size >= 0) {
352  _pixel_height = face->available_sizes[best_size].height;
353  _pixel_width = face->available_sizes[best_size].width;
354  error = FT_Set_Pixel_Sizes(face, _pixel_width, _pixel_height);
355  if (!error) {
356  _font_pixels_per_unit = _pixel_height * _points_per_unit / _point_size;
357  _scale_factor = _font_pixels_per_unit / _tex_pixels_per_unit;
358 
359  if (_scale_factor < 1.0) {
360  // No point in enlarging a fixed-point font.
361  _scale_factor = 1.0;
362  _tex_pixels_per_unit = _font_pixels_per_unit;
363  }
364  }
365  }
366  }
367 
368  if (error) {
369  pnmtext_cat.warning()
370  << "Unable to set " << get_name()
371  << " to " << _point_size << "pt at " << _dpi << " dpi.\n";
372  _line_height = 1.0f;
373  _face->release_face(face);
374  return false;
375  }
376 
377  _line_height = face->size->metrics.height / (_font_pixels_per_unit * 64.0f);
378 
379  // Determine the correct width for a space.
380  error = FT_Load_Char(face, ' ', FT_LOAD_DEFAULT);
381  if (error) {
382  // Space isn't defined. Oh well.
383  _space_advance = 0.25f * _line_height;
384 
385  } else {
386  _space_advance = face->glyph->advance.x / (_font_pixels_per_unit * 64.0f);
387  }
388 
389  _face->release_face(face);
390  return true;
391 }
392 
393 /**
394  * Renders a signed distance field to the PNMImage based on the contours.
395  */
396 void FreetypeFont::
397 render_distance_field(PNMImage &image, int outline, int min_x, int min_y) {
398  Contours::const_iterator ci;
399 
400  PN_stdfloat offset_x = -outline / _tex_pixels_per_unit;
401  PN_stdfloat offset_y = (image.get_y_size() - 1 - outline) / _tex_pixels_per_unit;;
402 
403  offset_x += min_x / (64.0f * _font_pixels_per_unit);
404  offset_y += min_y / (64.0f * _font_pixels_per_unit);
405 
406  PN_stdfloat scale = _tex_pixels_per_unit / (outline * 2);
407 
408  for (int y = 0; y < image.get_y_size(); ++y) {
409  LPoint2 p(0, offset_y - (y / _tex_pixels_per_unit));
410 
411  for (int x = 0; x < image.get_x_size(); ++x) {
412  p[0] = offset_x + (x / _tex_pixels_per_unit);
413 
414  PN_stdfloat min_dist_sq = 100000000;
415  int winding_number = 0;
416 
417  for (ci = _contours.begin(); ci != _contours.end(); ++ci) {
418  // Find the shortest distance between this point and the contour.
419  // Also keep track of the winding number, so we will know whether this
420  // point is inside or outside the polygon.
421  const Contour &contour = (*ci);
422 
423  for (size_t i = 1; i < contour._points.size(); ++i) {
424  const LPoint2 &begin = contour._points[i - 1]._p;
425  const LPoint2 &end = contour._points[i]._p;
426  PN_stdfloat radius = contour._points[i]._radius;
427 
428  LVector2 v = end - begin;
429  PN_stdfloat length_sq = v.length_squared();
430  PN_stdfloat dist_sq;
431 
432  if (length_sq == 0) {
433  dist_sq = (p - begin).length_squared();
434 
435  } else if (radius != 0) {
436  // Circular arc approximation.
437  LVector2 v1 = begin - contour._points[i]._center;
438  LVector2 v2 = end - contour._points[i]._center;
439  LVector2 vp = p - contour._points[i]._center;
440  PN_stdfloat dist_to_center = vp.length();
441  vp /= dist_to_center;
442  v1 /= radius;
443  v2 /= radius;
444  PN_stdfloat range = v1.dot(v2);
445  if (vp.dot(v1) > range && vp.dot(v2) > range) {
446  dist_sq = dist_to_center - radius;
447  bool inside = dist_sq < 0;
448  dist_sq *= dist_sq;
449 
450  // if (v1[0] * vp[1] - vp[0] * v1[1] < 0 && v2[0] * vp[1] -
451  // vp[0] * v2[1] < 0) { if (v1.signed_angle_deg(vp) <
452  // v1.signed_angle_deg(v2) && v1.signed_angle_deg(vp) > 0) {
453  if (begin[1] <= p[1]) {
454  if (end[1] > p[1]) {
455  if (inside != (v[0] * v1[1] > v[1] * v1[0])) {
456  ++winding_number;
457  }
458  }
459  } else {
460  if (end[1] <= p[1]) {
461  if (inside == (v[0] * v1[1] > v[1] * v1[0])) {
462  --winding_number;
463  }
464  }
465  }
466 
467  } else {
468  dist_sq = std::min((p - begin).length_squared(), (p - end).length_squared());
469  if (begin[1] <= p[1]) {
470  if (end[1] > p[1]) {
471  if ((v[0] * (p[1] - begin[1]) > v[1] * (p[0] - begin[0]))) {
472  ++winding_number;
473  }
474  }
475  } else {
476  if (end[1] <= p[1]) {
477  if ((v[0] * (p[1] - begin[1]) < v[1] * (p[0] - begin[0]))) {
478  --winding_number;
479  }
480  }
481  }
482  }
483 
484  } else {
485  // Just a straight line.
486  if (begin[1] <= p[1]) {
487  if (end[1] > p[1]) {
488  if ((v[0] * (p[1] - begin[1]) > v[1] * (p[0] - begin[0]))) {
489  ++winding_number;
490  }
491  }
492  } else {
493  if (end[1] <= p[1]) {
494  if ((v[0] * (p[1] - begin[1]) < v[1] * (p[0] - begin[0]))) {
495  --winding_number;
496  }
497  }
498  }
499 
500  PN_stdfloat t = v.dot(p - begin) / length_sq;
501  if (t <= 0.0) {
502  dist_sq = (p - begin).length_squared();
503  } else if (t >= 1.0) {
504  dist_sq = (p - end).length_squared();
505  } else {
506  dist_sq = (p - (begin + v * t)).length_squared();
507  }
508  }
509 
510  min_dist_sq = std::min(min_dist_sq, dist_sq);
511  }
512  }
513  // Determine the sign based on whether we're inside the contour.
514  int sign = (winding_number != 0) ? 1 : -1;
515 
516  PN_stdfloat signed_dist = csqrt(min_dist_sq) * sign;
517  image.set_gray(x, y, signed_dist * scale + (PN_stdfloat)0.5);
518  }
519  }
520 }
521 
522 /**
523  * Ask FreeType to extract the contours out of the outline description.
524  */
525 void FreetypeFont::
526 decompose_outline(FT_Outline &outline) {
527  FT_Outline_Funcs funcs;
528  memset(&funcs, 0, sizeof(funcs));
529  funcs.move_to = (FT_Outline_MoveTo_Func)outline_move_to;
530  funcs.line_to = (FT_Outline_LineTo_Func)outline_line_to;
531  funcs.conic_to = (FT_Outline_ConicTo_Func)outline_conic_to;
532  funcs.cubic_to = (FT_Outline_CubicTo_Func)outline_cubic_to;
533 
534  WindingOrder wo = _winding_order;
535  if (wo == WO_default) {
536  // If we weren't told an explicit winding order, ask FreeType to figure it
537  // out. Sometimes it appears to guess wrong.
538 #ifdef FT_ORIENTATION_FILL_RIGHT
539  if (FT_Outline_Get_Orientation(&outline) == FT_ORIENTATION_FILL_RIGHT) {
540  wo = WO_right;
541  } else {
542  wo = WO_left;
543  }
544 #else
545  // Hmm. Assign a right-winding (TTF) orientation if FreeType can't tell
546  // us.
547  wo = WO_right;
548 #endif // FT_ORIENTATION_FILL_RIGHT
549  }
550 
551  if (wo != WO_left) {
552  FT_Outline_Reverse(&outline);
553  }
554 
555  _contours.clear();
556  FT_Outline_Decompose(&outline, &funcs, (void *)this);
557 }
558 
559 /**
560  * A callback from FT_Outline_Decompose(). It marks the beginning of a new
561  * contour.
562  */
563 int FreetypeFont::
564 outline_move_to(const FT_Vector *to, void *user) {
565  FreetypeFont *self = (FreetypeFont *)user;
566 
567  // Convert from 26.6 pixel units to Panda units.
568  PN_stdfloat scale = 1.0f / (64.0f * self->_font_pixels_per_unit);
569  LPoint2 p = LPoint2(to->x, to->y) * scale;
570 
571  if (self->_contours.empty() ||
572  !self->_contours.back()._points.empty()) {
573  self->_contours.push_back(Contour());
574  }
575  self->_q = p;
576  return 0;
577 }
578 
579 /**
580  * A callback from FT_Outline_Decompose(). It marks a straight line in the
581  * contour.
582  */
583 int FreetypeFont::
584 outline_line_to(const FT_Vector *to, void *user) {
585  FreetypeFont *self = (FreetypeFont *)user;
586  nassertr(!self->_contours.empty(), 1);
587 
588  // Convert from 26.6 pixel units to Panda units.
589  PN_stdfloat scale = 1.0f / (64.0f * self->_font_pixels_per_unit);
590  LPoint2 p = LPoint2(to->x, to->y) * scale;
591 
592  // Compute the tangent: this is just the vector from the last point.
593  LVector2 t = (p - self->_q);
594  t.normalize();
595 
596  if (self->_contours.back()._points.empty()) {
597  self->_contours.back()._points.push_back(ContourPoint(self->_q, LVector2::zero(), t));
598  } else {
599  self->_contours.back()._points.back().connect_to(t);
600  }
601 
602  self->_contours.back()._points.push_back(ContourPoint(p, t, LVector2::zero()));
603  self->_q = p;
604  return 0;
605 }
606 
607 /**
608  * A callback from FT_Outline_Decompose(). It marks a parabolic (3rd-order)
609  * Bezier curve in the contour.
610  */
611 int FreetypeFont::
612 outline_conic_to(const FT_Vector *control,
613  const FT_Vector *to, void *user) {
614  FreetypeFont *self = (FreetypeFont *)user;
615  nassertr(!self->_contours.empty(), 1);
616 
617  // Convert from 26.6 pixel units to Panda units.
618  PN_stdfloat scale = 1.0f / (64.0f * self->_font_pixels_per_unit);
619 
620  LPoint2 c = LPoint2(control->x, control->y) * scale;
621  LPoint2 p = LPoint2(to->x, to->y) * scale;
622 
623  // The NurbsCurveEvaluator will evaluate the Bezier segment for us.
625  nce.local_object();
626  nce.set_order(3);
627  nce.reset(3);
628  nce.set_vertex(0, LVecBase3(self->_q[0], self->_q[1], 0.0f));
629  nce.set_vertex(1, LVecBase3(c[0], c[1], 0.0f));
630  nce.set_vertex(2, LVecBase3(p[0], p[1], 0.0f));
631 
632  self->_q = p;
633 
634  PT(NurbsCurveResult) ncr = nce.evaluate();
635  return self->outline_nurbs(ncr);
636 }
637 
638 /**
639  * A callback from FT_Outline_Decompose(). It marks a cubic (4th-order)
640  * Bezier curve in the contour.
641  */
642 int FreetypeFont::
643 outline_cubic_to(const FT_Vector *control1, const FT_Vector *control2,
644  const FT_Vector *to, void *user) {
645  FreetypeFont *self = (FreetypeFont *)user;
646  nassertr(!self->_contours.empty(), 1);
647 
648  // Convert from 26.6 pixel units to Panda units.
649  PN_stdfloat scale = 1.0f / (64.0f * self->_font_pixels_per_unit);
650 
651  LPoint2 c1 = LPoint2(control1->x, control1->y) * scale;
652  LPoint2 c2 = LPoint2(control2->x, control2->y) * scale;
653  LPoint2 p = LPoint2(to->x, to->y) * scale;
654 
655  // The NurbsCurveEvaluator will evaluate the Bezier segment for us.
657  nce.local_object();
658  nce.set_order(4);
659  nce.reset(4);
660  nce.set_vertex(0, LVecBase3(self->_q[0], self->_q[1], 0.0f));
661  nce.set_vertex(1, LVecBase3(c1[0], c1[1], 0.0f));
662  nce.set_vertex(2, LVecBase3(c2[0], c2[1], 0.0f));
663  nce.set_vertex(3, LVecBase3(p[0], p[1], 0.0f));
664 
665  self->_q = p;
666 
667  PT(NurbsCurveResult) ncr = nce.evaluate();
668  return self->outline_nurbs(ncr);
669 }
670 
671 /**
672  * Called internally by outline_cubic_to() and outline_conic_to().
673  */
674 int FreetypeFont::
675 outline_nurbs(NurbsCurveResult *ncr) {
676  // Sample it down so that the lines approximate the curve to within a
677  // "pixel."
678  ncr->adaptive_sample(1.0f / _font_pixels_per_unit);
679 
680  int num_samples = ncr->get_num_samples();
681 
682  bool needs_connect = false;
683  int start = 1;
684  if (_contours.back()._points.empty()) {
685  // If we haven't got the first point of this contour yet, we must add it
686  // now.
687  start = 0;
688  } else {
689  needs_connect = true;
690  }
691 
692  for (int i = start; i < num_samples; ++i) {
693  PN_stdfloat st = ncr->get_sample_t(i);
694  const LPoint3 &p = ncr->get_sample_point(i);
695 
696  PN_stdfloat st0 = st, st1 = st;
697  if (i > 0) {
698  st0 = ncr->get_sample_t(i - 1) * 0.1f + st * 0.9f;
699  }
700  if (i < num_samples - 1) {
701  st1 = ncr->get_sample_t(i + 1) * 0.1f + st * 0.9f;
702  }
703  // Compute the tangent by deltaing nearby points. Don't evaluate the
704  // tangent from the NURBS, since that doesn't appear to be reliable.
705  LPoint3 p0, p1;
706  ncr->eval_point(st0, p0);
707  ncr->eval_point(st1, p1);
708  LVector3 t = p1 - p0;
709  t.normalize();
710 
711  if (needs_connect) {
712  _contours.back()._points.back().connect_to(LVector2(t[0], t[1]));
713  needs_connect = false;
714  }
715 
716  _contours.back()._points.push_back(ContourPoint(p[0], p[1], t[0], t[1]));
717 
718  if (i > 0) {
719  // Approximate the curve using a circular arc. This is used in the
720  // signed distance field generation code. We do this by sampling a
721  // point in the middle of the segment and calculating the circle that
722  // goes through the three points.
723  LPoint2 v1 = ncr->get_sample_point(i - 1).get_xy();
724  LPoint3 v2;
725  ncr->eval_point((ncr->get_sample_t(i - 1) + ncr->get_sample_t(i)) / 2, v2);
726  PN_stdfloat temp = v1.length_squared();
727  PN_stdfloat bc = (v2[0]*v2[0] + v2[1]*v2[1] - temp) * (PN_stdfloat)0.5f;
728  PN_stdfloat cd = (temp - p[0]*p[0] - p[1]*p[1]) * (PN_stdfloat)0.5f;
729  PN_stdfloat det = (v2[0]-v1[0])*(v1[1]-p[1])-(v1[0]-p[0])*(v2[1]-v1[1]);
730  if (!IS_NEARLY_ZERO(det)) {
731  LPoint2 center;
732  center[0] = (bc*(v1[1]-p[1])-cd*(v2[1]-v1[1]));
733  center[1] = ((v2[0]-v1[0])*cd-(v1[0]-p[0])*bc);
734  center /= det;
735  _contours.back()._points.back()._center = center;
736  _contours.back()._points.back()._radius = (center - v1).length();
737  }
738  }
739  }
740 
741  return 0;
742 }
743 
744 /**
745  *
746  */
747 ostream &
748 operator << (ostream &out, FreetypeFont::WindingOrder wo) {
749  switch (wo) {
750  case FreetypeFont::WO_default:
751  return out << "default";
752  case FreetypeFont::WO_left:
753  return out << "left";
754  case FreetypeFont::WO_right:
755  return out << "right";
756 
757  case FreetypeFont::WO_invalid:
758  return out << "invalid";
759  }
760 
761  return out << "(**invalid FreetypeFont::WindingOrder(" << (int)wo << ")**)";
762 }
763 
764 /**
765  *
766  */
767 istream &
768 operator >> (istream &in, FreetypeFont::WindingOrder &wo) {
769  string word;
770  in >> word;
771 
772  wo = FreetypeFont::string_winding_order(word);
773  return in;
774 }
775 
776 
777 #endif // HAVE_FREETYPE
void set_gray_val(int x, int y, xelval gray)
Sets the gray component color at the indicated pixel.
Definition: pnmImage.I:456
The name of this class derives from the fact that we originally implemented it as a layer on top of t...
Definition: pnmImage.h:58
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void reset(int num_vertices)
Resets all the vertices and knots to their default values, and sets the curve up with the indicated n...
get_sample_point
Returns the point on the curve of the nth sample point generated by the previous call to adaptive_sam...
A hierarchy of directories and files that appears to be one continuous file system, even though the files may originate from several different sources that may not be related to the actual OS&#39;s file system.
This class is an abstraction for evaluating NURBS curves.
get_sample_t
Returns the t value of the nth sample point generated by the previous call to adaptive_sample().
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
bool resolve_filename(Filename &filename, const DSearchPath &searchpath, const std::string &default_extension=std::string()) const
Searches the given search path for the filename.
get_num_samples
Returns the number of sample points generated by the previous call to adaptive_sample().
void set_order(int order)
Sets the order of the curve.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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.
A base class for all things which can have a name.
Definition: namable.h:26
get_maxval
Returns the maximum channel value allowable for any pixel in this image; for instance, 255 for a typical 8-bit-per-channel image.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void set_vertex(int i, const LVecBase4 &vertex)
Sets the nth control vertex of the curve, as a vertex in 4-d homogeneous space.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
void set_gray(int x, int y, float gray)
Sets the gray component color at the indicated pixel.
Definition: pnmImage.I:835
static VirtualFileSystem * get_global_ptr()
Returns the default global VirtualFileSystem.
bool eval_point(PN_stdfloat t, LVecBase3 &point)
Computes the point on the curve corresponding to the indicated value in parametric time...
void local_object()
This function should be called, once, immediately after creating a new instance of some ReferenceCoun...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void adaptive_sample(PN_stdfloat tolerance)
Determines the set of subdivisions necessary to approximate the curve with a set of linear segments...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
The result of a NurbsCurveEvaluator.