|
|
@@ -20,10 +20,29 @@
|
|
|
|
|
|
#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_util.h"
|
|
|
#include "config_express.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;
|
|
|
|
|
|
@@ -229,6 +248,51 @@ get_glyph(int character, const TextGlyph *&glyph) {
|
|
|
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
|
|
|
// Access: Private
|
|
|
@@ -253,6 +317,9 @@ initialize() {
|
|
|
// require generating mipmaps, but does require hardware support.
|
|
|
_anisotropic_degree = text_anisotropic_degree;
|
|
|
|
|
|
+ _render_mode = text_render_mode;
|
|
|
+ _winding_order = WO_default;
|
|
|
+
|
|
|
_preferred_page = 0;
|
|
|
}
|
|
|
|
|
|
@@ -284,7 +351,7 @@ update_filters() {
|
|
|
////////////////////////////////////////////////////////////////////
|
|
|
DynamicTextGlyph *DynamicTextFont::
|
|
|
make_glyph(int character, int glyph_index) {
|
|
|
- if (!load_glyph(glyph_index)) {
|
|
|
+ if (!load_glyph(glyph_index, false)) {
|
|
|
return (DynamicTextGlyph *)NULL;
|
|
|
}
|
|
|
|
|
|
@@ -293,6 +360,79 @@ make_glyph(int character, int glyph_index) {
|
|
|
|
|
|
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 we got an empty bitmap, it's a special case.
|
|
|
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
|