Sfoglia il codice sorgente

DynamicTextFont::RenderMode

David Rose 19 anni fa
parent
commit
629b215aee

+ 12 - 0
panda/src/mathutil/triangulator.I

@@ -53,6 +53,18 @@ get_vertex(int n) const {
   return _vertices[n];
   return _vertices[n];
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: Triangulator::is_left_winding
+//       Access: Published
+//  Description: Returns true if the polygon vertices are listed in
+//               counterclockwise order, or false if they appear to be
+//               listed in clockwise order.
+////////////////////////////////////////////////////////////////////
+INLINE bool Triangulator::
+is_left_winding() const {
+  return check_left_winding(_polygon);
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Triangulator::Triangle::Constructor
 //     Function: Triangulator::Triangle::Constructor
 //       Access: Private
 //       Access: Private

+ 64 - 17
panda/src/mathutil/triangulator.cxx

@@ -72,9 +72,10 @@ clear_polygon() {
 //               This vertex should index into the vertex pool
 //               This vertex should index into the vertex pool
 //               established by repeated calls to add_vertex().
 //               established by repeated calls to add_vertex().
 //
 //
-//               The vertices should be listed in counterclockwise
-//               order, and should not be repeated.  In particular, do
-//               not repeat the first vertex at the end.
+//               The vertices may be listed in either clockwise or
+//               counterclockwise order.  Vertices should not be
+//               repeated.  In particular, do not repeat the first
+//               vertex at the end.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void Triangulator::
 void Triangulator::
 add_polygon_vertex(int index) {
 add_polygon_vertex(int index) {
@@ -99,8 +100,9 @@ begin_hole() {
 //               This vertex should index into the vertex pool
 //               This vertex should index into the vertex pool
 //               established by repeated calls to add_vertex().
 //               established by repeated calls to add_vertex().
 //
 //
-//               The vertices should be listed in clockwise order, and
-//               should not be repeated.
+//               The vertices may be listed in either clockwise or
+//               counterclockwise order.  Vertices should not be
+//               repeated.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void Triangulator::
 void Triangulator::
 add_hole_vertex(int index) {
 add_hole_vertex(int index) {
@@ -123,11 +125,11 @@ triangulate() {
   // Set up the list of segments.
   // Set up the list of segments.
   seg.clear();
   seg.clear();
   seg.push_back(segment_t());  // we don't use the first entry.
   seg.push_back(segment_t());  // we don't use the first entry.
-  make_segment(_polygon);
+  make_segment(_polygon, true);
 
 
   Holes::const_iterator hi;
   Holes::const_iterator hi;
   for (hi = _holes.begin(); hi != _holes.end(); ++hi) {
   for (hi = _holes.begin(); hi != _holes.end(); ++hi) {
-    make_segment(*hi);
+    make_segment(*hi, false);
   }
   }
 
 
   // Shuffle the segment index.
   // Shuffle the segment index.
@@ -305,30 +307,75 @@ get_triangle_v2(int n) const {
 #define LENGTH(v0) (sqrt((v0).x * (v0).x + (v0).y * (v0).y))
 #define LENGTH(v0) (sqrt((v0).x * (v0).x + (v0).y * (v0).y))
 
 
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: Triangulator::check_left_winding
+//       Access: Private
+//  Description: Returns true if the list of vertices is
+//               counter-clockwise, false if it is clockwise.
+////////////////////////////////////////////////////////////////////
+bool Triangulator::
+check_left_winding(const vector_int &range) const {
+  // We do this by computing the polygon's signed area.  If it comes
+  // out negative, the polygon is right-winding.
+
+  double area = 0.0;
+  size_t j = range.size() - 1;
+  for (size_t i = 0; i < range.size(); ++i) {
+    const LPoint2d &p0 = _vertices[range[j]];
+    const LPoint2d &p1 = _vertices[range[i]];
+    area += p0[0] * p1[1] - p0[1] * p1[0];
+    j = i;
+  }
+
+  return area >= 0.0;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Triangulator::make_segment
 //     Function: Triangulator::make_segment
 //       Access: Private
 //       Access: Private
 //  Description: Converts a linear list of integer vertices to a list
 //  Description: Converts a linear list of integer vertices to a list
-//               of segment_t.
+//               of segment_t.  If want_left_winding is true, the list
+//               is reversed if necessary to make it left-winding;
+//               otherwise, it is reversed to make it right-winding.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void Triangulator::
 void Triangulator::
-make_segment(const vector_int &range) {
+make_segment(const vector_int &range, bool want_left_winding) {
   int num_points = (int)range.size();
   int num_points = (int)range.size();
   nassertv(num_points >= 2);
   nassertv(num_points >= 2);
 
 
   int first = (int)seg.size();
   int first = (int)seg.size();
   int last = first + num_points - 1;
   int last = first + num_points - 1;
 
 
-  seg.push_back(segment_t(this, range[0], range[1],
-                          last, first + 1));
+  if (want_left_winding == check_left_winding(range)) {
+    // Keep it in its natural order.
+    int first = (int)seg.size();
+    int last = first + num_points - 1;
+    
+    seg.push_back(segment_t(this, range[0], range[1],
+                            last, first + 1));
+    
+    for (int i = 1; i < num_points - 1; ++i) {
+      seg.push_back(segment_t(this, range[i], range[i + 1],
+                              first + i - 1, first + i + 1));
+    }
+    
+    seg.push_back(segment_t(this, range[num_points - 1], range[0],
+                            last - 1, first));
+
+  } else {
+    // Reverse it.
+    seg.push_back(segment_t(this, range[0], range[num_points - 1],
+                            last, first + 1));
+    
+    for (int i = 1; i < num_points - 1; ++i) {
+      seg.push_back(segment_t(this, range[num_points - i], range[num_points - i - 1],
+                              first + i - 1, first + i + 1));
+    }
+    
+    seg.push_back(segment_t(this, range[1], range[0],
+                            last - 1, first));
 
 
-  for (int i = 1; i < num_points - 1; ++i) {
-    seg.push_back(segment_t(this, range[i], range[i + 1],
-                            first + i - 1, first + i + 1));
   }
   }
-
-  seg.push_back(segment_t(this, range[num_points - 1], range[0],
-                          last - 1, first));
 }
 }
   
   
 /* Return the next segment in the generated random ordering of all the */
 /* Return the next segment in the generated random ordering of all the */

+ 3 - 1
panda/src/mathutil/triangulator.h

@@ -52,6 +52,7 @@ PUBLISHED:
 
 
   void clear_polygon();
   void clear_polygon();
   void add_polygon_vertex(int index);
   void add_polygon_vertex(int index);
+  INLINE bool is_left_winding() const;
 
 
   void begin_hole();
   void begin_hole();
   void add_hole_vertex(int index);
   void add_hole_vertex(int index);
@@ -170,7 +171,8 @@ private:
 
 
   vector_int visited;
   vector_int visited;
 
 
-  void make_segment(const vector_int &range);
+  bool check_left_winding(const vector_int &range) const;
+  void make_segment(const vector_int &range, bool want_left_winding);
 
 
   int choose_segment();
   int choose_segment();
   static double math_log2(double v);
   static double math_log2(double v);

+ 60 - 4
panda/src/parametrics/nurbsCurveResult.I

@@ -28,7 +28,7 @@ INLINE NurbsCurveResult::
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: NurbsCurveResult::get_start_t
 //     Function: NurbsCurveResult::get_start_t
-//       Access: Public
+//       Access: Published
 //  Description: Returns the first legal value of t on the curve.
 //  Description: Returns the first legal value of t on the curve.
 //               Usually this is 0.0.
 //               Usually this is 0.0.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -39,7 +39,7 @@ get_start_t() const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: NurbsCurveResult::get_end_t
 //     Function: NurbsCurveResult::get_end_t
-//       Access: Public
+//       Access: Published
 //  Description: Returns the last legal value of t on the curve.
 //  Description: Returns the last legal value of t on the curve.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE float NurbsCurveResult::
 INLINE float NurbsCurveResult::
@@ -124,7 +124,7 @@ eval_extended_points(float t, int d, float result[], int num_values) {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: NurbsCurveResult::get_num_segments
 //     Function: NurbsCurveResult::get_num_segments
-//       Access: Public
+//       Access: Published
 //  Description: Returns the number of piecewise continuous segments
 //  Description: Returns the number of piecewise continuous segments
 //               within the curve.  This number is usually not
 //               within the curve.  This number is usually not
 //               important unless you plan to call
 //               important unless you plan to call
@@ -137,7 +137,7 @@ get_num_segments() const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: NurbsCurveResult::get_segment_t
 //     Function: NurbsCurveResult::get_segment_t
-//       Access: Public
+//       Access: Published
 //  Description: Accepts a t value in the range [0, 1], and assumed to
 //  Description: Accepts a t value in the range [0, 1], and assumed to
 //               be relative to the indicated segment (as in
 //               be relative to the indicated segment (as in
 //               eval_segment_point()), and returns the corresponding
 //               eval_segment_point()), and returns the corresponding
@@ -147,3 +147,59 @@ INLINE float NurbsCurveResult::
 get_segment_t(int segment, float t) const {
 get_segment_t(int segment, float t) const {
   return t * (_basis.get_to(segment) - _basis.get_from(segment)) + _basis.get_from(segment);
   return t * (_basis.get_to(segment) - _basis.get_from(segment)) + _basis.get_from(segment);
 }
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: NurbsCurveResult::get_num_samples
+//       Access: Published
+//  Description: Returns the number of sample points generated by the
+//               previous call to adaptive_sample().
+////////////////////////////////////////////////////////////////////
+INLINE int NurbsCurveResult::
+get_num_samples() const {
+  return (int)_adaptive_result.size();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: NurbsCurveResult::get_sample_t
+//       Access: Published
+//  Description: Returns the t value of the nth sample point generated
+//               by the previous call to adaptive_sample().
+////////////////////////////////////////////////////////////////////
+INLINE float NurbsCurveResult::
+get_sample_t(int n) const {
+  nassertr(n >= 0 && n < (int)_adaptive_result.size(), 0.0f);
+  return _adaptive_result[n]._t;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: NurbsCurveResult::get_sample_point
+//       Access: Published
+//  Description: Returns the point on the curve of the nth sample
+//               point generated by the previous call to
+//               adaptive_sample().
+//
+//               For tangents, or extended points, you should use
+//               get_sample_t() and pass it into eval_tangent() or
+//               eval_extended_point().
+////////////////////////////////////////////////////////////////////
+INLINE const LPoint3f &NurbsCurveResult::
+get_sample_point(int n) const {
+  nassertr(n >= 0 && n < (int)_adaptive_result.size(), LPoint3f::zero());
+  return _adaptive_result[n]._point;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: NurbsCurveResult::get_segment_t
+//       Access: Public
+//  Description: Accepts a t value in the range [0, 1], and assumed to
+//               be relative to the indicated segment (as in
+//               eval_segment_point()), and returns the corresponding
+//               t value in the entire curve (as in eval_point()).
+////////////////////////////////////////////////////////////////////
+INLINE NurbsCurveResult::AdaptiveSample::
+AdaptiveSample(float t, const LPoint3f &point) :
+  _t(t),
+  _point(point)
+{
+}
+

+ 78 - 0
panda/src/parametrics/nurbsCurveResult.cxx

@@ -199,6 +199,41 @@ eval_segment_extended_points(int segment, float t, int d,
   }
   }
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: NurbsCurveResult::adaptive_sample
+//       Access: Published
+//  Description: Determines the set of subdivisions necessary to
+//               approximate the curve with a set of linear segments,
+//               no point of which is farther than tolerance units
+//               from the actual curve.
+//
+//               After this call, you may walk through the resulting
+//               set of samples with get_num_samples(),
+//               get_sample_t(), and get_sample_point().
+////////////////////////////////////////////////////////////////////
+void NurbsCurveResult::
+adaptive_sample(float tolerance) {
+  float tolerance_2 = tolerance * tolerance;
+  _adaptive_result.clear();
+
+  LPoint3f p0, p1;
+
+  int num_segments = _basis.get_num_segments();
+  for (int segment = 0; segment < num_segments; ++segment) {
+    eval_segment_point(segment, 0.0f, p0);
+    if (segment == 0 || !p0.almost_equal(p1)) {
+      // We explicitly push the first point, and the boundary point
+      // anytime the segment boundary is discontinuous.
+      _adaptive_result.push_back(AdaptiveSample(_basis.get_from(segment), p0));
+    }
+
+    eval_segment_point(segment, 1.0f, p1);
+
+    // Then we recusrively get the remaining points in the segment.
+    r_adaptive_sample(segment, 0.0f, p0, 1.0f, p1, tolerance_2);
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: NurbsCurveResult::find_segment
 //     Function: NurbsCurveResult::find_segment
 //       Access: Private
 //       Access: Private
@@ -264,3 +299,46 @@ r_find_segment(float t, int top, int bot) const {
   }
   }
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: NurbsCurveResult::r_adaptive_sample
+//       Access: Private
+//  Description: Recursively subdivides a potential evaluation of the
+//               segment until it is found to be within tolerance.
+//               This will add everything up to and including t1, but
+//               excluding t0.
+////////////////////////////////////////////////////////////////////
+void NurbsCurveResult::
+r_adaptive_sample(int segment, float t0, const LPoint3f &p0, 
+                  float t1, const LPoint3f &p1, float tolerance_2) {
+  float tmid = (t0 + t1) * 0.5f;
+  LPoint3f pmid;
+  eval_segment_point(segment, tmid, pmid);
+
+  if (sqr_dist_to_line(pmid, p0, p1 - p0) > tolerance_2) {
+    // The line is still too curved--subdivide.
+    r_adaptive_sample(segment, t0, p0, tmid, pmid, tolerance_2);
+    r_adaptive_sample(segment, tmid, pmid, t1, p1, tolerance_2);
+
+  } else {
+    // The line is sufficiently close.  Stop here.
+    _adaptive_result.push_back(AdaptiveSample(_basis.scale_t(segment, t1), p1));
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: NurbsCurveResult::sqr_dist_to_line
+//       Access: Private, Static
+//  Description: A support function for r_adaptive_sample(), this
+//               computes the minimum distance from a point to a line,
+//               and returns the distance squared.
+////////////////////////////////////////////////////////////////////
+float NurbsCurveResult::
+sqr_dist_to_line(const LPoint3f &point, const LPoint3f &origin, 
+                 const LVector3f &vec) {
+  LVector3f norm = vec;
+  norm.normalize();
+  LVector3f d = point - origin;
+  float hyp_2 = d.length_squared();
+  float leg = d.dot(norm);
+  return hyp_2 - leg * leg;
+}

+ 20 - 1
panda/src/parametrics/nurbsCurveResult.h

@@ -22,6 +22,7 @@
 #include "pandabase.h"
 #include "pandabase.h"
 #include "referenceCount.h"
 #include "referenceCount.h"
 #include "nurbsBasisVector.h"
 #include "nurbsBasisVector.h"
+#include "vector_float.h"
 
 
 class NurbsVertex;
 class NurbsVertex;
 
 
@@ -63,11 +64,21 @@ PUBLISHED:
   void eval_segment_extended_points(int segment, float t, int d,
   void eval_segment_extended_points(int segment, float t, int d,
                                     float result[], int num_values) const;
                                     float result[], int num_values) const;
   INLINE float get_segment_t(int segment, float t) const;
   INLINE float get_segment_t(int segment, float t) const;
+
+  void adaptive_sample(float tolerance);
+  INLINE int get_num_samples() const;
+  INLINE float get_sample_t(int n) const;
+  INLINE const LPoint3f &get_sample_point(int n) const;
   
   
 private:
 private:
   int find_segment(float t);
   int find_segment(float t);
   int r_find_segment(float t, int top, int bot) const;
   int r_find_segment(float t, int top, int bot) const;
 
 
+  void r_adaptive_sample(int segment, float t0, const LPoint3f &p0, 
+                         float t1, const LPoint3f &p1, float tolerance_2);
+  static float sqr_dist_to_line(const LPoint3f &point, const LPoint3f &origin, 
+                                const LVector3f &vec);
+
   NurbsBasisVector _basis;
   NurbsBasisVector _basis;
   const NurbsVertex *_verts;
   const NurbsVertex *_verts;
 
 
@@ -77,10 +88,18 @@ private:
   typedef pvector<LMatrix4f> ComposedGeom;
   typedef pvector<LMatrix4f> ComposedGeom;
   ComposedGeom _composed;
   ComposedGeom _composed;
 
 
-
   int _last_segment;
   int _last_segment;
   float _last_from;
   float _last_from;
   float _last_to;
   float _last_to;
+
+  class AdaptiveSample {
+  public:
+    INLINE AdaptiveSample(float t, const LPoint3f &point);
+    float _t;
+    LPoint3f _point;
+  };
+  typedef pvector<AdaptiveSample> AdaptiveResult;
+  AdaptiveResult _adaptive_result;
 };
 };
 
 
 #include "nurbsCurveResult.I"
 #include "nurbsCurveResult.I"

+ 7 - 1
panda/src/pnmtext/freetypeFont.cxx

@@ -170,12 +170,18 @@ unload_font() {
 //               false otherwise.
 //               false otherwise.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool FreetypeFont::
 bool FreetypeFont::
-load_glyph(int glyph_index) {
+load_glyph(int glyph_index, bool prerender) {
   int flags = FT_LOAD_RENDER;
   int flags = FT_LOAD_RENDER;
   if (!_native_antialias) { 
   if (!_native_antialias) { 
     flags |= FT_LOAD_MONOCHROME;
     flags |= FT_LOAD_MONOCHROME;
   }
   }
 
 
+  if (!prerender) {
+    // If we want to render as an outline font, don't pre-render it to
+    // a bitmap.
+    flags = 0;
+  }
+
   int error = FT_Load_Glyph(_face, glyph_index, flags);
   int error = FT_Load_Glyph(_face, glyph_index, flags);
   if (error) {
   if (error) {
     pnmtext_cat.error()
     pnmtext_cat.error()

+ 2 - 2
panda/src/pnmtext/freetypeFont.h

@@ -76,7 +76,7 @@ public:
   INLINE static float get_points_per_inch();
   INLINE static float get_points_per_inch();
 
 
 protected:
 protected:
-  bool load_glyph(int glyph_index);
+  bool load_glyph(int glyph_index, bool prerender = true);
   void copy_bitmap_to_pnmimage(const FT_Bitmap &bitmap, PNMImage &image);
   void copy_bitmap_to_pnmimage(const FT_Bitmap &bitmap, PNMImage &image);
 
 
 private:
 private:
@@ -99,7 +99,7 @@ protected:
 
 
   FT_Face _face;
   FT_Face _face;
 
 
-private:
+protected:
   bool _font_loaded;
   bool _font_loaded;
 
 
   // This string is used to hold the data read from the font file in
   // This string is used to hold the data read from the font file in

+ 2 - 1
panda/src/text/Sources.pp

@@ -7,7 +7,8 @@
   #define TARGET text
   #define TARGET text
   #define LOCAL_LIBS \
   #define LOCAL_LIBS \
     putil gobj pgraph linmath \
     putil gobj pgraph linmath \
-    pnmtext pnmimage gsgbase mathutil
+    pnmtext pnmimage gsgbase mathutil \
+    parametrics
     
     
   #define COMBINED_SOURCES $[TARGET]_composite1.cxx $[TARGET]_composite2.cxx 
   #define COMBINED_SOURCES $[TARGET]_composite1.cxx $[TARGET]_composite2.cxx 
 
 

+ 4 - 0
panda/src/text/config_text.cxx

@@ -182,6 +182,10 @@ ConfigVariableEnum<Texture::WrapMode> text_wrap_mode
 ("text-wrap-mode", Texture::WM_border_color,
 ("text-wrap-mode", Texture::WM_border_color,
  PRC_DESC("The default wrap mode for dynamic text fonts"));
  PRC_DESC("The default wrap mode for dynamic text fonts"));
 
 
+ConfigVariableEnum<DynamicTextFont::RenderMode> text_render_mode
+("text-render-mode", DynamicTextFont::RM_texture,
+ PRC_DESC("The default render mode for dynamic text fonts"));
+
 
 
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////

+ 50 - 1
panda/src/text/dynamicTextFont.I

@@ -102,7 +102,8 @@ get_pixels_per_unit() const {
 //               larger than 1.0 to improve the font's antialiasing
 //               larger than 1.0 to improve the font's antialiasing
 //               (since FreeType doesn't really do a swell job of
 //               (since FreeType doesn't really do a swell job of
 //               antialiasing by itself).  There is some performance
 //               antialiasing by itself).  There is some performance
-//               implication for setting this different than 1.0.
+//               implication for setting this different than 1.0, but
+//               it is probably small.
 //
 //
 //               This should only be called before any characters have
 //               This should only be called before any characters have
 //               been requested out of the font, or immediately after
 //               been requested out of the font, or immediately after
@@ -360,6 +361,54 @@ get_anisotropic_degree() const {
   return _anisotropic_degree;
   return _anisotropic_degree;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: DynamicTextFont::set_render_mode
+//       Access: Published
+//  Description: Specifies the way the glyphs on this particular font
+//               are generated.  The default is RM_texture, which is
+//               the only mode supported for bitmap fonts. Other modes
+//               are possible for most modern fonts.
+////////////////////////////////////////////////////////////////////
+INLINE void DynamicTextFont::
+set_render_mode(DynamicTextFont::RenderMode render_mode) {
+  _render_mode = render_mode;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DynamicTextFont::get_render_mode
+//       Access: Published
+//  Description: Returns the way the glyphs on this particular font
+//               are generated.  See set_render_mode().
+////////////////////////////////////////////////////////////////////
+INLINE DynamicTextFont::RenderMode DynamicTextFont::
+get_render_mode() const {
+  return _render_mode;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DynamicTextFont::set_winding_order
+//       Access: Published
+//  Description: Specifies an explicitly winding order on this
+//               particular font.  This is only necessary if the
+//               render_mode is RM_polygon or RM_solid, and only if
+//               FreeType appears to guess wrong on this font.
+//               Normally, you should leave this at WO_default.
+////////////////////////////////////////////////////////////////////
+INLINE void DynamicTextFont::
+set_winding_order(DynamicTextFont::WindingOrder winding_order) {
+  _winding_order = winding_order;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DynamicTextFont::get_winding_order
+//       Access: Published
+//  Description: Returns the winding order set via set_winding_order().
+////////////////////////////////////////////////////////////////////
+INLINE DynamicTextFont::WindingOrder DynamicTextFont::
+get_winding_order() const {
+  return _winding_order;
+}
+
 INLINE ostream &
 INLINE ostream &
 operator << (ostream &out, const DynamicTextFont &dtf) {
 operator << (ostream &out, const DynamicTextFont &dtf) {
   return out << dtf.get_name();
   return out << dtf.get_name();

+ 442 - 1
panda/src/text/dynamicTextFont.cxx

@@ -20,10 +20,29 @@
 
 
 #ifdef HAVE_FREETYPE
 #ifdef HAVE_FREETYPE
 
 
+#undef interface  // I don't know where this symbol is defined, but it interferes with FreeType.
+#include FT_OUTLINE_H 
+#include FT_STROKER_H
+#include FT_BBOX_H
+#ifdef FT_BITMAP_H
+#include FT_BITMAP_H
+#endif
+
 #include "config_text.h"
 #include "config_text.h"
 #include "config_util.h"
 #include "config_util.h"
 #include "config_express.h"
 #include "config_express.h"
 #include "virtualFileSystem.h"
 #include "virtualFileSystem.h"
+#include "geomVertexData.h"
+#include "geomVertexFormat.h"
+#include "geomVertexWriter.h"
+#include "geomLinestrips.h"
+#include "geomTriangles.h"
+#include "renderState.h"
+#include "string_utils.h"
+#include "triangulator.h"
+#include "nurbsCurveEvaluator.h"
+#include "nurbsCurveResult.h"
+//#include "renderModeAttrib.h"
 
 
 TypeHandle DynamicTextFont::_type_handle;
 TypeHandle DynamicTextFont::_type_handle;
 
 
@@ -229,6 +248,51 @@ get_glyph(int character, const TextGlyph *&glyph) {
   return (glyph_index != 0 && glyph != (DynamicTextGlyph *)NULL);
   return (glyph_index != 0 && glyph != (DynamicTextGlyph *)NULL);
 }
 }
 
 
+
+////////////////////////////////////////////////////////////////////
+//     Function: DynamicTextFont::string_render_mode
+//       Access: Public
+//  Description: Returns the RenderMode value associated with the given
+//               string representation, or RM_invalid if the string
+//               does not match any known RenderMode value.
+////////////////////////////////////////////////////////////////////
+DynamicTextFont::RenderMode DynamicTextFont::
+string_render_mode(const string &string) {
+  if (cmp_nocase_uh(string, "texture") == 0) {
+    return RM_texture;
+  } else if (cmp_nocase_uh(string, "wireframe") == 0) {
+    return RM_wireframe;
+  } else if (cmp_nocase_uh(string, "polygon") == 0) {
+    return RM_polygon;
+  } else if (cmp_nocase_uh(string, "extruded") == 0) {
+    return RM_extruded;
+  } else if (cmp_nocase_uh(string, "solid") == 0) {
+    return RM_solid;
+  } else {
+    return RM_invalid;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DynamicTextFont::string_winding_order
+//       Access: Public
+//  Description: Returns the WindingOrder value associated with the given
+//               string representation, or WO_invalid if the string
+//               does not match any known WindingOrder value.
+////////////////////////////////////////////////////////////////////
+DynamicTextFont::WindingOrder DynamicTextFont::
+string_winding_order(const string &string) {
+  if (cmp_nocase_uh(string, "default") == 0) {
+    return WO_default;
+  } else if (cmp_nocase_uh(string, "left") == 0) {
+    return WO_left;
+  } else if (cmp_nocase_uh(string, "right") == 0) {
+    return WO_right;
+  } else {
+    return WO_invalid;
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: DynamicTextFont::initialize
 //     Function: DynamicTextFont::initialize
 //       Access: Private
 //       Access: Private
@@ -253,6 +317,9 @@ initialize() {
   // require generating mipmaps, but does require hardware support.
   // require generating mipmaps, but does require hardware support.
   _anisotropic_degree = text_anisotropic_degree;
   _anisotropic_degree = text_anisotropic_degree;
 
 
+  _render_mode = text_render_mode;
+  _winding_order = WO_default;
+
   _preferred_page = 0;
   _preferred_page = 0;
 }
 }
 
 
@@ -284,7 +351,7 @@ update_filters() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 DynamicTextGlyph *DynamicTextFont::
 DynamicTextGlyph *DynamicTextFont::
 make_glyph(int character, int glyph_index) {
 make_glyph(int character, int glyph_index) {
-  if (!load_glyph(glyph_index)) {
+  if (!load_glyph(glyph_index, false)) {
     return (DynamicTextGlyph *)NULL;
     return (DynamicTextGlyph *)NULL;
   }
   }
 
 
@@ -293,6 +360,79 @@ make_glyph(int character, int glyph_index) {
 
 
   float advance = slot->advance.x / 64.0;
   float advance = slot->advance.x / 64.0;
 
 
+  if (_render_mode != RM_texture && 
+      slot->format == ft_glyph_format_outline) {
+    // Re-stroke the glyph to make it an outline glyph.
+    /*
+    FT_Stroker stroker;
+    FT_Stroker_New(_face->memory, &stroker);
+    FT_Stroker_Set(stroker, 16 * 16, FT_STROKER_LINECAP_BUTT,
+                   FT_STROKER_LINEJOIN_ROUND, 0);
+
+    FT_Stroker_ParseOutline(stroker, &slot->outline, 0);
+
+    FT_UInt num_points, num_contours;
+    FT_Stroker_GetCounts(stroker, &num_points, &num_contours);
+
+    FT_Outline border;
+    FT_Outline_New(_ft_library, num_points, num_contours, &border);
+    border.n_points = 0;
+    border.n_contours = 0;
+    FT_Stroker_Export(stroker, &border);
+    FT_Stroker_Done(stroker);
+
+    FT_Outline_Done(_ft_library, &slot->outline);
+    memcpy(&slot->outline, &border, sizeof(border));
+    */
+
+    // Ask FreeType to extract the contours out of the outline
+    // description.
+    FT_Outline_Funcs funcs;
+    memset(&funcs, 0, sizeof(funcs));
+    funcs.move_to = (FT_Outline_MoveTo_Func)outline_move_to;
+    funcs.line_to = (FT_Outline_LineTo_Func)outline_line_to;
+    funcs.conic_to = (FT_Outline_ConicTo_Func)outline_conic_to;
+    funcs.cubic_to = (FT_Outline_CubicTo_Func)outline_cubic_to;
+
+    WindingOrder wo = _winding_order;
+    if (wo == WO_default) {
+      // If we weren't told an explicit winding order, ask FreeType to
+      // figure it out.  Sometimes it appears to guess wrong.
+      if (FT_Outline_Get_Orientation(&slot->outline) == FT_ORIENTATION_FILL_RIGHT) {
+        wo = WO_right;
+      } else {
+        wo = WO_left;
+      }
+    }
+
+    if (wo != WO_left) {
+      FT_Outline_Reverse(&slot->outline);
+    }
+
+    _contours.clear();
+    FT_Outline_Decompose(&slot->outline, &funcs, (void *)this);
+
+    PT(DynamicTextGlyph) glyph = 
+      new DynamicTextGlyph(character, advance / _font_pixels_per_unit);
+    switch (_render_mode) {
+    case RM_wireframe:
+      render_wireframe_contours(glyph);
+      return glyph;
+
+    case RM_polygon:
+      render_polygon_contours(glyph);
+      return glyph;
+
+    default:
+      break;
+    }
+  }
+
+  // Render the glyph if necessary.
+  if (slot->format != ft_glyph_format_bitmap) {
+    FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL);
+  }
+
   if (bitmap.width == 0 || bitmap.rows == 0) {
   if (bitmap.width == 0 || bitmap.rows == 0) {
     // If we got an empty bitmap, it's a special case.
     // If we got an empty bitmap, it's a special case.
     PT(DynamicTextGlyph) glyph = 
     PT(DynamicTextGlyph) glyph = 
@@ -485,4 +625,305 @@ slot_glyph(int character, int x_size, int y_size) {
   }
   }
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: DynamicTextFont::render_wireframe_contours
+//       Access: Private
+//  Description: Converts from the _contours list to an actual glyph
+//               geometry, as a wireframe render.
+////////////////////////////////////////////////////////////////////
+void DynamicTextFont::
+render_wireframe_contours(DynamicTextGlyph *glyph) {
+  PT(GeomVertexData) vdata = new GeomVertexData
+    (string(), GeomVertexFormat::get_v3(),
+     Geom::UH_static);
+  GeomVertexWriter vertex(vdata, InternalName::get_vertex());
+
+  PT(GeomLinestrips) lines = new GeomLinestrips(Geom::UH_static);
+
+  Contours::const_iterator ci;
+  for (ci = _contours.begin(); ci != _contours.end(); ++ci) {
+    const Contour &contour = (*ci);
+    Points::const_iterator pi;
+
+    for (pi = contour._points.begin(); pi != contour._points.end(); ++pi) {
+      const LPoint2f &p = (*pi);
+      vertex.add_data3f(p[0], 0.0f, p[1]);
+    }
+
+    lines->add_next_vertices(contour._points.size());
+    lines->close_primitive();
+  }
+
+  glyph->set_geom(vdata, lines, RenderState::make_empty());
+  _contours.clear();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DynamicTextFont::render_polygon_contours
+//       Access: Private
+//  Description: Converts from the _contours list to an actual glyph
+//               geometry, as a polygon render.
+////////////////////////////////////////////////////////////////////
+void DynamicTextFont::
+render_polygon_contours(DynamicTextGlyph *glyph) {
+  PT(GeomVertexData) vdata = new GeomVertexData
+    (string(), GeomVertexFormat::get_v3(),
+     Geom::UH_static);
+  GeomVertexWriter vertex(vdata, InternalName::get_vertex());
+
+  PT(GeomTriangles) tris = new GeomTriangles(Geom::UH_static);
+  Triangulator t;
+
+  // First, build up the list of vertices, and determine which
+  // contours are solid and which are holes.
+  Contours::iterator ci;
+  for (ci = _contours.begin(); ci != _contours.end(); ++ci) {
+    Contour &contour = (*ci);
+
+    t.clear_polygon();
+    contour._start_vertex = t.get_num_vertices();
+    for (size_t i = 0; i < contour._points.size() - 1; ++i) {
+      const LPoint2f &p = contour._points[i];
+      vertex.add_data3f(p[0], 0.0f, p[1]);
+      int vi = t.add_vertex(p[0], p[1]);
+      t.add_polygon_vertex(vi);
+    }
+
+    contour._is_solid = t.is_left_winding();
+  }
+
+  // Now go back and generate the actual triangles.
+  for (ci = _contours.begin(); ci != _contours.end(); ++ci) {
+    const Contour &contour = (*ci);
+    
+    if (contour._is_solid && !contour._points.empty()) {
+      t.clear_polygon();
+      for (size_t i = 0; i < contour._points.size() - 1; ++i) {
+        t.add_polygon_vertex(contour._start_vertex + i);
+      }
+
+      // Also add all the holes to each polygon.
+      Contours::iterator cj;
+      for (cj = _contours.begin(); cj != _contours.end(); ++cj) {
+        Contour &hole = (*cj);
+        if (!hole._is_solid && !hole._points.empty()) {
+          t.begin_hole();
+          for (size_t j = 0; j < hole._points.size() - 1; ++j) {
+            t.add_hole_vertex(hole._start_vertex + j);
+          }
+        }
+      }
+
+      t.triangulate();
+      int num_triangles = t.get_num_triangles();
+      for (int ti = 0; ti < num_triangles; ++ti) {
+        tris->add_vertex(t.get_triangle_v0(ti));
+        tris->add_vertex(t.get_triangle_v1(ti));
+        tris->add_vertex(t.get_triangle_v2(ti));
+        tris->close_primitive();
+      }
+    }
+  }
+
+  glyph->set_geom(vdata, tris, RenderState::make_empty());
+  //  glyph->set_geom(vdata, tris, RenderState::make(RenderModeAttrib::make(RenderModeAttrib::M_wireframe)));
+  _contours.clear();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DynamicTextFont::outline_move_to
+//       Access: Private, Static
+//  Description: A callback from FT_Outline_Decompose().  It marks the
+//               beginning of a new contour.
+////////////////////////////////////////////////////////////////////
+int DynamicTextFont::
+outline_move_to(const FT_Vector *to, void *user) {
+  DynamicTextFont *self = (DynamicTextFont *)user;
+
+  // Convert from 26.6 pixel units to Panda units.
+  float scale = 1.0f / (64.0f * self->_font_pixels_per_unit);
+  LPoint2f p = LPoint2f(to->x, to->y) * scale;
+
+  self->_contours.push_back(Contour());
+  self->_contours.back()._points.push_back(p);
+  return 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DynamicTextFont::outline_line_to
+//       Access: Private, Static
+//  Description: A callback from FT_Outline_Decompose().  It marks a
+//               straight line in the contour.
+////////////////////////////////////////////////////////////////////
+int DynamicTextFont::
+outline_line_to(const FT_Vector *to, void *user) {
+  DynamicTextFont *self = (DynamicTextFont *)user;
+  nassertr(!self->_contours.empty(), 1);
+
+  // Convert from 26.6 pixel units to Panda units.
+  float scale = 1.0f / (64.0f * self->_font_pixels_per_unit);
+  LPoint2f p = LPoint2f(to->x, to->y) * scale;
+
+  self->_contours.back()._points.push_back(p);
+  return 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DynamicTextFont::outline_conic_to
+//       Access: Private, Static
+//  Description: A callback from FT_Outline_Decompose().  It marks a
+//               parabolic (3rd-order) Bezier curve in the contour.
+////////////////////////////////////////////////////////////////////
+int DynamicTextFont::
+outline_conic_to(const FT_Vector *control,
+                 const FT_Vector *to, void *user) {
+  DynamicTextFont *self = (DynamicTextFont *)user;
+  nassertr(!self->_contours.empty(), 1);
+  const LPoint2f &q = self->_contours.back()._points.back();
+
+  // Convert from 26.6 pixel units to Panda units.
+  float scale = 1.0f / (64.0f * self->_font_pixels_per_unit);
+
+  LPoint2f c = LPoint2f(control->x, control->y) * scale;
+  LPoint2f p = LPoint2f(to->x, to->y) * scale;
+
+  // The NurbsCurveEvaluator will evaluate the Bezier segment for us.
+  NurbsCurveEvaluator nce;
+  nce.local_object();
+  nce.set_order(3);
+  nce.reset(3);
+  nce.set_vertex(0, LVecBase3f(q[0], q[1], 0.0f));
+  nce.set_vertex(1, LVecBase3f(c[0], c[1], 0.0f));
+  nce.set_vertex(2, LVecBase3f(p[0], p[1], 0.0f));
+
+  PT(NurbsCurveResult) ncr = nce.evaluate();
+
+  // Sample it down so that the lines approximate the curve to within
+  // a "pixel."
+  ncr->adaptive_sample(1.0f / self->_font_pixels_per_unit);
+
+  int num_samples = ncr->get_num_samples();
+  for (int i = 1; i < num_samples; ++i) {
+    const LPoint3f &p = ncr->get_sample_point(i);
+    self->_contours.back()._points.push_back(LPoint2f(p[0], p[1]));
+  }
+  return 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DynamicTextFont::outline_cubic_to
+//       Access: Private, Static
+//  Description: A callback from FT_Outline_Decompose().  It marks a
+//               cubic (4th-order) Bezier curve in the contour.
+////////////////////////////////////////////////////////////////////
+int DynamicTextFont::
+outline_cubic_to(const FT_Vector *control1, const FT_Vector *control2,
+                 const FT_Vector *to, void *user) {
+  DynamicTextFont *self = (DynamicTextFont *)user;
+  nassertr(!self->_contours.empty(), 1);
+  const LPoint2f &q = self->_contours.back()._points.back();
+
+  // Convert from 26.6 pixel units to Panda units.
+  float scale = 1.0f / (64.0f * self->_font_pixels_per_unit);
+
+  LPoint2f c1 = LPoint2f(control1->x, control1->y) * scale;
+  LPoint2f c2 = LPoint2f(control2->x, control2->y) * scale;
+  LPoint2f p = LPoint2f(to->x, to->y) * scale;
+
+  // The NurbsCurveEvaluator will evaluate the Bezier segment for us.
+  NurbsCurveEvaluator nce;
+  nce.local_object();
+  nce.set_order(4);
+  nce.reset(4);
+  nce.set_vertex(0, LVecBase3f(q[0], q[1], 0.0f));
+  nce.set_vertex(1, LVecBase3f(c1[0], c1[1], 0.0f));
+  nce.set_vertex(2, LVecBase3f(c2[0], c2[1], 0.0f));
+  nce.set_vertex(3, LVecBase3f(p[0], p[1], 0.0f));
+
+  PT(NurbsCurveResult) ncr = nce.evaluate();
+
+  // Sample it down so that the lines approximate the curve to within
+  // a "pixel."
+  ncr->adaptive_sample(1.0f / self->_font_pixels_per_unit);
+
+  int num_samples = ncr->get_num_samples();
+  for (int i = 1; i < num_samples; ++i) {
+    const LPoint3f &p = ncr->get_sample_point(i);
+    self->_contours.back()._points.push_back(LPoint2f(p[0], p[1]));
+  }
+  return 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DynamicTextFont::RenderMode output operator
+//  Description:
+////////////////////////////////////////////////////////////////////
+ostream &
+operator << (ostream &out, DynamicTextFont::RenderMode rm) {
+  switch (rm) {
+  case DynamicTextFont::RM_texture:
+    return out << "texture";
+  case DynamicTextFont::RM_wireframe:
+    return out << "wireframe";
+  case DynamicTextFont::RM_polygon:
+    return out << "polygon";
+  case DynamicTextFont::RM_extruded:
+    return out << "extruded";
+  case DynamicTextFont::RM_solid:
+    return out << "solid";
+
+  case DynamicTextFont::RM_invalid:
+    return out << "invalid";
+  }
+
+  return out << "(**invalid DynamicTextFont::RenderMode(" << (int)rm << ")**)";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DynamicTextFont::RenderMode input operator
+//  Description:
+////////////////////////////////////////////////////////////////////
+istream &
+operator >> (istream &in, DynamicTextFont::RenderMode &rm) {
+  string word;
+  in >> word;
+
+  rm = DynamicTextFont::string_render_mode(word);
+  return in;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DynamicTextFont::WindingOrder output operator
+//  Description:
+////////////////////////////////////////////////////////////////////
+ostream &
+operator << (ostream &out, DynamicTextFont::WindingOrder wo) {
+  switch (wo) {
+  case DynamicTextFont::WO_default:
+    return out << "default";
+  case DynamicTextFont::WO_left:
+    return out << "left";
+  case DynamicTextFont::WO_right:
+    return out << "right";
+
+  case DynamicTextFont::WO_invalid:
+    return out << "invalid";
+  }
+
+  return out << "(**invalid DynamicTextFont::WindingOrder(" << (int)wo << ")**)";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DynamicTextFont::WindingOrder input operator
+//  Description:
+////////////////////////////////////////////////////////////////////
+istream &
+operator >> (istream &in, DynamicTextFont::WindingOrder &wo) {
+  string word;
+  in >> word;
+
+  wo = DynamicTextFont::string_winding_order(word);
+  return in;
+}
+
 #endif  // HAVE_FREETYPE
 #endif  // HAVE_FREETYPE

+ 67 - 0
panda/src/text/dynamicTextFont.h

@@ -45,6 +45,34 @@
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 class EXPCL_PANDA DynamicTextFont : public TextFont, public FreetypeFont {
 class EXPCL_PANDA DynamicTextFont : public TextFont, public FreetypeFont {
 PUBLISHED:
 PUBLISHED:
+  enum RenderMode {
+    // Each glyph is a single textured rectangle
+    RM_texture,
+
+    // Each glyph is a lot of line segments
+    RM_wireframe,
+
+    // Each glyph is a lot of triangles
+    RM_polygon,
+
+    // a 3-D outline, like a cookie cutter
+    RM_extruded,
+
+    // combination of RM_extruded and RM_polygon
+    RM_solid,
+
+    // Returned by string_render_mode() for an invalid match.
+    RM_invalid,
+  };
+
+  enum WindingOrder {
+    WO_default,
+    WO_left,
+    WO_right,
+
+    WO_invalid,
+  };
+
   DynamicTextFont(const Filename &font_filename, int face_index = 0);
   DynamicTextFont(const Filename &font_filename, int face_index = 0);
   DynamicTextFont(const char *font_data, int data_length, int face_index);
   DynamicTextFont(const char *font_data, int data_length, int face_index);
   virtual ~DynamicTextFont();
   virtual ~DynamicTextFont();
@@ -84,6 +112,11 @@ PUBLISHED:
   INLINE void set_anisotropic_degree(int anisotropic_degree);
   INLINE void set_anisotropic_degree(int anisotropic_degree);
   INLINE int get_anisotropic_degree() const;
   INLINE int get_anisotropic_degree() const;
 
 
+  INLINE void set_render_mode(RenderMode render_mode);
+  INLINE RenderMode get_render_mode() const;
+  INLINE void set_winding_order(WindingOrder winding_order);
+  INLINE WindingOrder get_winding_order() const;
+
   int get_num_pages() const;
   int get_num_pages() const;
   DynamicTextPage *get_page(int n) const;
   DynamicTextPage *get_page(int n) const;
 
 
@@ -95,6 +128,9 @@ PUBLISHED:
 public:
 public:
   virtual bool get_glyph(int character, const TextGlyph *&glyph);
   virtual bool get_glyph(int character, const TextGlyph *&glyph);
 
 
+  static RenderMode string_render_mode(const string &string);
+  static WindingOrder string_winding_order(const string &string);
+
 private:
 private:
   void initialize();
   void initialize();
   void update_filters();
   void update_filters();
@@ -103,6 +139,17 @@ private:
   void copy_pnmimage_to_texture(const PNMImage &image, DynamicTextGlyph *glyph);
   void copy_pnmimage_to_texture(const PNMImage &image, DynamicTextGlyph *glyph);
   DynamicTextGlyph *slot_glyph(int character, int x_size, int y_size);
   DynamicTextGlyph *slot_glyph(int character, int x_size, int y_size);
 
 
+  void render_wireframe_contours(DynamicTextGlyph *glyph);
+  void render_polygon_contours(DynamicTextGlyph *glyph);
+
+  static int outline_move_to(const FT_Vector *to, void *user);
+  static int outline_line_to(const FT_Vector *to, void *user);
+  static int outline_conic_to(const FT_Vector *control,
+                              const FT_Vector *to, void *user);
+  static int outline_cubic_to(const FT_Vector *control1, 
+                              const FT_Vector *control2, 
+                              const FT_Vector *to, void *user);
+
   int _texture_margin;
   int _texture_margin;
   float _poly_margin;
   float _poly_margin;
   int _page_x_size, _page_y_size;
   int _page_x_size, _page_y_size;
@@ -111,6 +158,9 @@ private:
   Texture::FilterType _magfilter;
   Texture::FilterType _magfilter;
   int _anisotropic_degree;
   int _anisotropic_degree;
 
 
+  RenderMode _render_mode;
+  WindingOrder _winding_order;
+
   typedef pvector< PT(DynamicTextPage) > Pages;
   typedef pvector< PT(DynamicTextPage) > Pages;
   Pages _pages;
   Pages _pages;
   int _preferred_page;
   int _preferred_page;
@@ -127,6 +177,18 @@ private:
   typedef pvector< PT(DynamicTextGlyph) > EmptyGlyphs;
   typedef pvector< PT(DynamicTextGlyph) > EmptyGlyphs;
   EmptyGlyphs _empty_glyphs;
   EmptyGlyphs _empty_glyphs;
 
 
+  typedef pvector<LPoint2f> Points;
+
+  class Contour {
+  public:
+    Points _points;
+    bool _is_solid;
+    int _start_vertex;
+  };
+
+  typedef pvector<Contour> Contours;
+  Contours _contours;
+
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {
     return _type_handle;
     return _type_handle;
@@ -149,6 +211,11 @@ private:
 
 
 INLINE ostream &operator << (ostream &out, const DynamicTextFont &dtf);
 INLINE ostream &operator << (ostream &out, const DynamicTextFont &dtf);
 
 
+EXPCL_PANDA ostream &operator << (ostream &out, DynamicTextFont::RenderMode rm);
+EXPCL_PANDA istream &operator >> (istream &in, DynamicTextFont::RenderMode &rm);
+EXPCL_PANDA ostream &operator << (ostream &out, DynamicTextFont::WindingOrder wo);
+EXPCL_PANDA istream &operator >> (istream &in, DynamicTextFont::WindingOrder &wo);
+
 #include "dynamicTextFont.I"
 #include "dynamicTextFont.I"
 
 
 #endif  // HAVE_FREETYPE
 #endif  // HAVE_FREETYPE

+ 26 - 0
panda/src/text/dynamicTextGlyph.cxx

@@ -168,4 +168,30 @@ make_geom(int bitmap_top, int bitmap_left, float advance, float poly_margin,
 }
 }
 
 
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: DynamicTextGlyph::set_geom
+//       Access: Public
+//  Description: Sets the geom from a pre-built object.
+////////////////////////////////////////////////////////////////////
+void DynamicTextGlyph::
+set_geom(GeomVertexData *vdata, GeomPrimitive *prim, 
+         const RenderState *state) {
+  // This function is called when _geom_count = 1, because it was
+  // constructed via the empty Glyph constructor.
+  nassertv(_geom_count == 1);
+  _geom_count--;
+
+  PT(Geom) geom = new GeomTextGlyph(this, vdata);
+  geom->add_primitive(prim);
+  _geom = geom;
+  
+  // The above will increment our _geom_count to 1.  Reset it back
+  // down to 0, since our own internal Geom doesn't count.
+  nassertv(_geom_count == 1);
+  _geom_count--;
+  
+  _state = state;
+}
+
+
 #endif  // HAVE_FREETYPE
 #endif  // HAVE_FREETYPE

+ 2 - 0
panda/src/text/dynamicTextGlyph.h

@@ -53,6 +53,8 @@ public:
   void make_geom(int top, int left, float advance, float poly_margin,
   void make_geom(int top, int left, float advance, float poly_margin,
                  float tex_x_size, float tex_y_size,
                  float tex_x_size, float tex_y_size,
                  float font_pixels_per_unit, float tex_pixels_per_unit);
                  float font_pixels_per_unit, float tex_pixels_per_unit);
+  void set_geom(GeomVertexData *vdata, GeomPrimitive *prim, 
+                const RenderState *state);
 
 
   DynamicTextPage *_page;
   DynamicTextPage *_page;
   int _geom_count;
   int _geom_count;

+ 2 - 0
panda/src/text/textProperties.cxx

@@ -289,6 +289,8 @@ load_default_font() {
   // Loading the compiled-in FreeType font is relatively easy.
   // Loading the compiled-in FreeType font is relatively easy.
   _default_font = new DynamicTextFont((const char *)default_font_data, 
   _default_font = new DynamicTextFont((const char *)default_font_data, 
                                       default_font_size, 0);
                                       default_font_size, 0);
+  // The compiled-in font seems to confuse FreeType about its winding order.
+  ((DynamicTextFont *)_default_font.p())->set_winding_order(DynamicTextFont::WO_left);
 
 
 #else
 #else
   // The compiled-in Bam font requires creating a BamFile object to
   // The compiled-in Bam font requires creating a BamFile object to