// Copyright (C) 2010, Guy Barrand. All rights reserved.
// See the file tools.license for terms.

#ifndef tools_sg_text_freetype
#define tools_sg_text_freetype

#include <tools/sg/base_freetype>
#include <tools/sg/render_action>
#include <tools/sg/pick_action>
#include <tools/sg/bbox_action>
#include <tools/sg/enums>
#include <tools/lina/vec3d>
#include <tools/fmanip>
#include <tools/nostream>
#include <tools/lina/box_3f>

#include "../glutess/glutess"

#include <map> //for errors

#ifndef SWIG
#ifndef TOOLS_USE_OUREX_FREETYPE
#include <ft2build.h>
#else
#include <ourex_ft2build.h>

/*
#ifdef TOOLS_MEM
// the below coworks with raising cpp TOOLS_MEM in
//   ourex/freetype/src/builds/unix/ftsystem.c
#include <tools/mem>
extern "C" {
  typedef void* __FT_Memory;
  void* ft_alloc(__FT_Memory,long size){
    tools::mem::increment(tools::s_malloc().c_str());
    return ::malloc(size);
  }
  void* ft_realloc(__FT_Memory,long,long new_size,void* block){
    if(block==NULL) tools::mem::increment(tools::s_malloc().c_str());
    return ::realloc(block,new_size);
  }
  void ft_free(__FT_Memory,void* block){
    if(block!=NULL) tools::mem::decrement(tools::s_malloc().c_str());
    ::free(block);
  }
}
#endif
*/

#endif
#include FT_FREETYPE_H
#include FT_GLYPH_H
#include FT_OUTLINE_H
#endif

//#define TOOLS_SG_TEXT_FREETYPE_DEBUG

namespace tools {
namespace sg {

class text_freetype : public tools::sg::base_freetype {
  TOOLS_NODE(text_freetype,tools::sg::text_freetype,tools::sg::base_freetype)
protected:
  enum wndg_type {
    wndg_ccw,
    wndg_cw,
    wndg_not_done
  };
protected: //gstos
  virtual unsigned int create_gsto(std::ostream& a_out,tools::sg::gl_manager& a_mgr) {
    std::vector<float> gsto_data;

   {tools_vforcit(line_t,m_lines,it) {
      const line_t& item = *it;
      unsigned int pos = item.first;
      unsigned int num = item.second;

      if(num<2) {
        a_out << "tools::sg::text_freetype::create_gsto :"
              << " strange line with " << num << " points."
              << std::endl;
        continue; //do we have the case num = 1 ?
      }

      const float* data = tools::vec_data<float>(m_xys)+pos;

      // data is a line_strip(), convert it to lines.

      unsigned int nsegs = num-1;

      unsigned int ngsto = nsegs*2*3; //3 = have a z (Windows GL).
      unsigned int sz = gsto_data.size();
      gsto_data.resize(sz+ngsto);
      float* pxyz = tools::vec_data<float>(gsto_data)+sz;
  
      tools::gl::line_strip_to_lines_2to3(num,data,pxyz);
    }}

    m_gsto_lines_sz = gsto_data.size();

   {tools_vforcit(gl_triangle_t,m_triangles,it) {
      const std::pair<GLUenum,triangle_t>& item = *it;
      unsigned int pos = item.second.first;
      unsigned int num = item.second.second;

      if(num<3) {
        a_out << "tools::sg::text_freetype::create_gsto :"
              << " strange triangle primitive with " << num << " points."
              << " Primitive kind is " << (*it).first << "."
              << std::endl;
        continue;
      }

      const float* data = tools::vec_data<float>(m_xys)+pos;

      if((*it).first==tools::gl::triangles()) {

        unsigned int ntri = num/3;

        unsigned int ngsto = ntri*3*3;
        unsigned int sz = gsto_data.size();
        gsto_data.resize(sz+ngsto);
        float* pxyz = tools::vec_data<float>(gsto_data)+sz;
  
        tools::gl::cvt_2to3(num,data,pxyz);  

      } else if((*it).first==tools::gl::triangle_fan()) {

        unsigned int ntri = num-2;

        unsigned int ngsto = ntri*3*3;
        unsigned int sz = gsto_data.size();
        gsto_data.resize(sz+ngsto);
        float* pxyz = tools::vec_data<float>(gsto_data)+sz;
  
        tools::gl::triangle_fan_to_triangles_2to3(num,data,pxyz);

      } else if((*it).first==tools::gl::triangle_strip()) {

        unsigned int ntri = num-2;

        unsigned int ngsto = ntri*3*3;
        unsigned int sz = gsto_data.size();
        gsto_data.resize(sz+ngsto);
        float* pxyz = tools::vec_data<float>(gsto_data)+sz;
  
        tools::gl::triangle_strip_to_triangles_2to3(num,data,pxyz);

      } else {
        a_out << "tools::sg::text_freetype::create_gsto :"
              << " unknown triangle primitive kind " << (*it).first << "."
              << std::endl;
      }

    }}

    m_gsto_sz = gsto_data.size();

    if(gsto_data.empty()) {
      //a_out << "tools::sg::text_freetype::create_gsto :"
      //      << " empty buffer."
      //      << std::endl;
      return 0;
    }

    return a_mgr.create_gsto_from_data(gsto_data);
  }

public:
  virtual void render(tools::sg::render_action& a_action) {
    if(touched()) {
      update_sg(a_action.out(),font.touched());
      reset_touched();
    }

    const tools::sg::state& state = a_action.state();

         if(m_wndg==wndg_ccw) a_action.set_winding(tools::sg::winding_ccw);
    else if(m_wndg==wndg_cw) a_action.set_winding(tools::sg::winding_cw);

    if(state.m_use_gsto) {
      unsigned int _id = get_gsto_id(a_action.out(),a_action.gl_manager());
      if(_id) {
        a_action.begin_gsto(_id);
        unsigned int sz_tris = m_gsto_sz-m_gsto_lines_sz;
        if(m_gsto_lines_sz) {
          a_action.set_line_smooth(true);
          a_action.draw_gsto_v(tools::gl::lines(),m_gsto_lines_sz/3,0);
          a_action.set_polygon_offset(true);
        }
        if(m_gsto_lines_sz && sz_tris) {
          a_action.set_line_smooth(state.m_GL_LINE_SMOOTH);
          a_action.set_polygon_offset(state.m_GL_POLYGON_OFFSET_FILL);
        }
        tools::sg::bufpos pos = m_gsto_lines_sz*sizeof(float);
        a_action.draw_gsto_v(tools::gl::triangles(),sz_tris/3,pos);
        a_action.end_gsto();

        a_action.set_line_smooth(state.m_GL_LINE_SMOOTH);
        a_action.set_polygon_offset(state.m_GL_POLYGON_OFFSET_FILL);
        a_action.set_winding(state.m_winding);
        return;
      } else {
        // use immediate rendering.
      }
    } else {
      clean_gstos(&a_action.gl_manager());
    }

    // immediate rendering :

    ///////////////////////////////////////////////////////////
    /// lines /////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////

    a_action.set_line_smooth(true);

   {tools_vforcit(line_t,m_lines,it) {
      const line_t& item = *it;
      unsigned int pos = item.first;
      unsigned int num = item.second;
      if(!num) continue;
      //a_out << "tools::sg::text_freetype::render :"
      //      << " num points " << num
      //      << std::endl;

      const float* data = tools::vec_data<float>(m_xys)+pos;

      a_action.draw_vertex_array_xy(tools::gl::line_strip(),num*2,data);
    }}

    if(m_lines.size()) a_action.set_polygon_offset(true);

    ///////////////////////////////////////////////////////////
    /// triangles /////////////////////////////////////////////
    ///////////////////////////////////////////////////////////
   {tools_vforcit(gl_triangle_t,m_triangles,it) {
      const std::pair<GLUenum,triangle_t>& item = *it;
      unsigned int pos = item.second.first;
      unsigned int num = item.second.second;
      if(!num) continue;
      //a_out << "tools::sg::text_freetype::render :"
      //      << " num points " << num
      //      << std::endl;

      const float* data = tools::vec_data<float>(m_xys)+pos;

      a_action.draw_vertex_array_xy((*it).first,num*2,data);
    }}

    a_action.set_line_smooth(state.m_GL_LINE_SMOOTH);
    a_action.set_polygon_offset(state.m_GL_POLYGON_OFFSET_FILL);
    a_action.set_winding(state.m_winding);
  }

  virtual void pick(tools::sg::pick_action& a_action) {
    if(touched()) {
      update_sg(a_action.out(),font.touched());
      reset_touched();
    }

/*
    //OPTIMIZATION : pick on the bounding box ?
    if(m_face) {
      tools::box3f box;
      get_bounds(m_face,height,strings.values(),box);

      vec3f mn = box.mn();
      vec3f mx = box.mx();

      mn = mtx*mn;
      mx = mtx*mx;

      if(a_action.is_inside(b[0],b[1])) {
        //we have a pick.
        a_action.set_done(true);
        a_action.set_node(this);
        return;
      }

      if(a_action.intersect(p1[0],p1[1],p2[0],p2[1],p3[0],p3[1])) {
        a_action.set_done(true);
        a_action.set_node(this);
        return;
      }          
    }
    return;    
*/

    ///////////////////////////////////////////////////////////
    /// lines /////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////

   {tools_vforcit(line_t,m_lines,it) {
      const line_t& item = *it;
      unsigned int pos = item.first;
      unsigned int num = item.second;
      const float* data = tools::vec_data<float>(m_xys)+pos;
      if(a_action.add__line_strip_xy(*this,2*num,data,true)) return;
    }}


    ///////////////////////////////////////////////////////////
    /// triangles /////////////////////////////////////////////
    ///////////////////////////////////////////////////////////
   {tools_vforcit(gl_triangle_t,m_triangles,it) {
      const std::pair<GLUenum,triangle_t>& item = *it;
      unsigned int pos = item.second.first;
      unsigned int num = item.second.second;
      const float* data = tools::vec_data<float>(m_xys)+pos;
      if(a_action.add__primitive_xy(*this,item.first,2*num,data,true)) return;
    }}

  }

  virtual void bbox(tools::sg::bbox_action& a_action) {
    if(touched()) {
      update_sg(a_action.out(),font.touched());
      reset_touched();
    }

   {tools_vforcit(line_t,m_lines,it) {
      const line_t& item = *it;
      unsigned int num = item.second;
      const float* data = tools::vec_data<float>(m_xys)+item.first;

      float px,py,pz;
      float* pos = (float*)data;
      for(unsigned int index=0;index<num;index++) {
        px = *pos;pos++;
        py = *pos;pos++;
        pz = 0;
        a_action.add_one_point(px,py,pz);
      }  

    }}

    ///////////////////////////////////////////////////////////
    /// triangles /////////////////////////////////////////////
    ///////////////////////////////////////////////////////////
   {tools_vforcit(gl_triangle_t,m_triangles,it) {
      const std::pair<GLUenum,triangle_t>& item = *it;
      unsigned int num = item.second.second;
      const float* data = tools::vec_data<float>(m_xys)+item.second.first;

      float px,py,pz;
      float* pos = (float*)data;
      for(unsigned int index=0;index<num;index++) {
        px = *pos;pos++;
        py = *pos;pos++;
        pz = 0;
        a_action.add_one_point(px,py,pz);
      }  
    }}

  }

public:
  text_freetype()
  :parent()

  ,m_library(0)
  ,m_face(0)
  ,m_encoding_offset(0)
  ,m_verbose(false)
  ,m_scale(1)
  ,m_pos(0)
  {
    if(!initialize()){} //throw
  }
  virtual ~text_freetype(){
    if(m_face) ::FT_Done_Face(m_face);
    if(m_library) ::FT_Done_FreeType(m_library);
    clear_trids();
  }
public:
  text_freetype(const text_freetype& a_from)
  :parent(a_from)

  ,m_library(0)
  ,m_face(0)
  ,m_encoding_offset(0)
  ,m_verbose(a_from.m_verbose)
  ,m_scale(1)
  ,m_pos(0)
  {
    if(!initialize()){} //throw
  }

  text_freetype& operator=(const text_freetype& a_from){
    parent::operator=(a_from);
    if(&a_from==this) return *this;

    if(m_face) {::FT_Done_Face(m_face);m_face = 0;}
    if(m_library) {::FT_Done_FreeType(m_library);m_library = 0;}
    clear_trids();

    m_encoding_offset = 0;
    m_verbose = a_from.m_verbose;
    m_scale = 1;

    if(!initialize()){} //throw

    return *this;
  }

public: //tools::sg::base_text :
  virtual float ascent(float a_height) const {
    tools::nostream out;
    if(!m_face) {
      const_cast<text_freetype*>(this)->load_face(out);
    }
    if(!m_face) return 0;
    float value;
    ascent(out,m_face,a_height,value);
    return value;
  }

  virtual float descent(float a_height) const {
    tools::nostream out;
    if(!m_face) {
      const_cast<text_freetype*>(this)->load_face(out);
    }
    if(!m_face) return 0;
    float value;
    descent(out,m_face,a_height,value);
    return value;
  }

  virtual float y_advance(float a_height) const {
    tools::nostream out;
    if(!m_face) {
      const_cast<text_freetype*>(this)->load_face(out);
    }
    if(!m_face) return 0;
    float value;
    y_advance(out,m_face,a_height,value);
    return value;
  }

  virtual void get_bounds(float a_height,
                          float& a_mn_x,float& a_mn_y,float& a_mn_z,
                          float& a_mx_x,float& a_mx_y,float& a_mx_z) const {
    tools::nostream out;
    if(!m_face) {
      const_cast<text_freetype*>(this)->load_face(out);
    }
    if(!m_face) return;
    if(strings.values().size()) {
      get_bounds(out,m_face,a_height,strings.values(),
                 a_mn_x,a_mn_y,a_mn_z,
                 a_mx_x,a_mx_y,a_mx_z);
    } else if(unitext.values().size()) {
      get_bounds(out,m_face,a_height,unitext.values(),
                 a_mn_x,a_mn_y,a_mn_z,
                 a_mx_x,a_mx_y,a_mx_z);
    }    
  }

  virtual bool truncate(const std::string& a_string,
                        float a_height,float a_cut_width,
                        std::string& a_out) const {
    a_out.clear();
    tools::nostream out;
    if(!m_face) {
      const_cast<text_freetype*>(this)->load_face(out);
    }
    if(!m_face) return false;
    return truncate(out,m_face,a_height,a_string,a_cut_width,a_out);
  }

  void dump_unitext(std::ostream& a_out) {
    //unitext.values().size()
    a_out << "unitext size : " << unitext.values().size() << std::endl;
    tools_vforcit(uniline,unitext.values(),vit) {
      const uniline& line = *vit;
      a_out << "beg line :" << std::endl;
      //a_out << line << std::endl;
      tools_vforcit(unichar,line,it) {
        a_out << ((unsigned int)*it) << std::endl;
      }  
      a_out << "end line." << std::endl;
    }
  }

protected:
  bool initialize() { //called from constructors.
    FT_Error error = ::FT_Init_FreeType(&m_library);
    if(error) {
      //m_out << "tools::sg::text_freetype :"
      //      << " error : " << serror(error) << "."
      //      << std::endl;
      m_library = 0;
      return false;
    }
    // cast because of const/not const according freetype version.
    // (Recent version have "const FT_Vector*" in args).
    m_funcs.move_to = (FT_Outline_MoveToFunc)outline_move_to;
    m_funcs.line_to = (FT_Outline_LineToFunc)outline_line_to;
    m_funcs.conic_to = (FT_Outline_ConicToFunc)outline_conic_to;
    m_funcs.cubic_to = (FT_Outline_CubicToFunc)outline_cubic_to;
    m_funcs.shift = 0;
    m_funcs.delta = 0;

    // Comment from OGLFT :
    //   Default number of steps to break TrueType and Type1 arcs into.
    //   (Note: this looks good to me, anyway)
    m_steps = 4;
    m_delta = 1.0f /(float)m_steps;
    m_delta2 = m_delta * m_delta;
    m_delta3 = m_delta2 * m_delta;

    return true;
  }

  ////////////////////////////////////////////////////////
  /// outline lines //////////////////////////////////////
  ////////////////////////////////////////////////////////
  enum update_what {
    faces = 0,
    lines = 1,
    faces_and_lines
  };

  void update_sg(std::ostream& a_out,bool a_load_font) {
    if(a_load_font) load_face(a_out);

    clean_gstos(); //must reset for all gl_manager.

    if(!m_face) return;

    //a_out << "tools::sg::text_freetype::update_sg :"
    //      << " font file opened."
    //      << std::endl;

    m_encoding_offset = 0;
    if(!encoding_offset(m_face,m_encoding_offset)) {
      a_out << "tools::sg::text_freetype::update_sg :"
            << " encoding_offset failed."
            << std::endl;
      ::FT_Done_Face(m_face);
      m_face = 0;
      return;
    }

    m_xys.clear();
    m_pos = 0;
    m_lines.clear();
    m_triangles.clear();

    m_wndg = wndg_not_done;

    update_what _what = lines;
    //if(modeling==tools::sg::font_filled) _what = faces_and_lines;
    if(modeling==tools::sg::font_filled) _what = faces;

    if((_what==faces)||(_what==faces_and_lines)) outline_triangles_2_gl(a_out);

    if((_what==lines)||(_what==faces_and_lines)) outline_lines_2_gl(a_out);

    if(vjust==tools::sg::bottom) {
    } else if(vjust==tools::sg::middle) {
      float mn_x,mn_y,mn_z;
      float mx_x,mx_y,mx_z;
      get_bounds(height,mn_x,mn_y,mn_z,mx_x,mx_y,mx_z);
      float szy = mx_y - mn_y;

     {tools_vforit(line_t,m_lines,it) {
        line_t& item = *it;
        unsigned int pos = item.first;
        unsigned int npt = item.second;
        float* data = tools::vec_data<float>(m_xys)+pos+1;
        for(unsigned int i=0;i<npt;i++,data+=2) *data -= 0.5F * szy;
      }}

     {tools_vforit(gl_triangle_t,m_triangles,it) {
        std::pair<GLUenum,triangle_t>& item = *it;
        unsigned int pos = item.second.first;
        unsigned int npt = item.second.second;
        float* data = tools::vec_data<float>(m_xys)+pos+1;
        for(unsigned int i=0;i<npt;i++,data+=2) *data -= 0.5F * szy;
      }}

    } else if(vjust==tools::sg::top) {
      float mn_x,mn_y,mn_z;
      float mx_x,mx_y,mx_z;
      get_bounds(height,mn_x,mn_y,mn_z,mx_x,mx_y,mx_z);
      float szy = mx_y - mn_y;

     {tools_vforit(line_t,m_lines,it) {
        line_t& item = *it;
        unsigned int pos = item.first;
        unsigned int npt = item.second;
        float* data = tools::vec_data<float>(m_xys)+pos+1;
        for(unsigned int i=0;i<npt;i++,data+=2) *data -= szy;
      }}

     {tools_vforit(gl_triangle_t,m_triangles,it) {
        std::pair<GLUenum,triangle_t>& item = *it;
        unsigned int pos = item.second.first;
        unsigned int npt = item.second.second;
        float* data = tools::vec_data<float>(m_xys)+pos+1;
        for(unsigned int i=0;i<npt;i++,data+=2) *data -= szy;
      }}

    }

  }

  void outline_lines_2_gl(std::ostream& a_out) {
    if(!set_char_size(a_out,m_face,height,m_scale)) return;
    FT_Pos face_height = m_face->size->metrics.height; //FT_Pos (long)

    m_tobj = 0; //IMPORTANT.

    if(strings.values().size()) {
      float yline = 0;
      tools_vforcit(std::string,strings.values(),vit) {
        const std::string& line = *vit;
        //a_out << line << std::endl;
        m_trans_x = 0;
        m_trans_y = yline;
        unsigned int ibeg = m_lines.size(); //for hjust.
        tools_sforcit(line,it) {
          if(!char_outline_2_gl(a_out,*it + m_encoding_offset)) return;
        }
        yline += -float(face_height)*m_scale;   //height >0

       {float sx = m_trans_x;
        if(hjust==tools::sg::center) {
          unsigned int num = m_lines.size();
          for(unsigned int index=ibeg;index<num;index++) {
            line_t& item = m_lines[index];
            unsigned int pos = item.first;
            unsigned int npt = item.second;
            float* data = tools::vec_data<float>(m_xys)+pos;
            for(unsigned int i=0;i<npt;i++,data+=2) *data -= 0.5F * sx;
          }
        } else if(hjust==tools::sg::right) {
          unsigned int num = m_lines.size();
          for(unsigned int index=ibeg;index<num;index++) {
            line_t& item = m_lines[index];
            unsigned int pos = item.first;
            unsigned int npt = item.second;
            float* data = tools::vec_data<float>(m_xys)+pos;
            for(unsigned int i=0;i<npt;i++,data+=2) *data -= sx;
          }
        }}
      }

    } else if(unitext.values().size()) {
      float yline = 0;
      tools_vforcit(uniline,unitext.values(),vit) {
        const uniline& line = *vit;
        //a_out << line << std::endl;
        m_trans_x = 0;
        m_trans_y = yline;
        unsigned int ibeg = m_lines.size(); //for hjust.
        tools_vforcit(unichar,line,it) {
          if(!char_outline_2_gl(a_out,*it)) return;
        }  
        yline += -float(face_height)*m_scale; //height>0

       {float sx = m_trans_x;
        if(hjust==tools::sg::center) {
          unsigned int num = m_lines.size();
          for(unsigned int index=ibeg;index<num;index++) {
            line_t& item = m_lines[index];
            unsigned int pos = item.first;
            unsigned int npt = item.second;
            float* data = tools::vec_data<float>(m_xys)+pos;
            for(unsigned int i=0;i<npt;i++,data+=2) *data -= 0.5F*sx;
          }
        } else if(hjust==tools::sg::center) {
          unsigned int num = m_lines.size();
          for(unsigned int index=ibeg;index<num;index++) {
            line_t& item = m_lines[index];
            unsigned int pos = item.first;
            unsigned int npt = item.second;
            float* data = tools::vec_data<float>(m_xys)+pos;
            for(unsigned int i=0;i<npt;i++,data+=2) *data -= sx;
          }
        }}
      }
    }
  }

  bool char_outline_2_gl(std::ostream& a_out,unsigned int a_unichar) {
    FT_ULong charcode = a_unichar; //charcode is UTF-32.
    FT_UInt glyph_index = ::FT_Get_Char_Index(m_face,charcode);
    //NOTE : if not found -> glyph_index = 0 which is the "missing glyph".
    if((FT_Long)glyph_index>=m_face->num_glyphs) {
      a_out << "tools::sg::text_freetype::char_outline_2_gl :"
            << " FT_Get_Char_Index : failed for char : " << a_unichar 
            << std::endl;
      ::FT_Done_Face(m_face);
      m_face = 0;
      return false;
    }
  
   {FT_Error error = ::FT_Load_Glyph(m_face,glyph_index,load_flags());
    if(error) {
      a_out << "tools::sg::text_freetype::char_outline_2_gl :"
            << " for character " << a_unichar
            << ",FT_Load_Glyph : error : " << serror(error)
            << std::endl;
      ::FT_Done_Face(m_face);
      m_face = 0;
      return false;
    }}
  
    //FT_GlyphSlot FT_Face.glyph;
    if(m_face->glyph->format!=FT_GLYPH_FORMAT_OUTLINE) {
      a_out << "tools::sg::text_freetype::char_outline_2_gl :"
            << " for font " << tools::sout(font.value())
            << " and for character " << a_unichar
            << " glyph not at format outline."
            << std::endl;
      ::FT_Done_Face(m_face);
      m_face = 0;
      return false;
    }
  
    FT_Outline outline = m_face->glyph->outline;
  
   {FT_Error error = ::FT_Outline_Decompose(&outline,&m_funcs,this);
    if(error) {
      a_out << "tools::sg::text_freetype::char_outline_2_gl :"
            << " for character " << a_unichar
            << ",FT_Outline_Decompose : error : " << serror(error)
            << std::endl;
      ::FT_Done_Face(m_face);
      m_face = 0;
      return false;
    }}
  
    flush_line();
  
    m_trans_x += float(m_face->glyph->advance.x)*m_scale;
    m_trans_y += float(m_face->glyph->advance.y)*m_scale;

   {wndg_type wdg =
      ((outline.flags & FT_OUTLINE_REVERSE_FILL)?wndg_ccw:wndg_cw);
    if(m_wndg==wndg_not_done) {
      m_wndg = wdg;
    } else if(m_wndg!=wdg) {
      a_out << "tools::sg::text_freetype::char_outline_2_gl :"
            << " for character " << a_unichar << ", winding anomaly."
            << std::endl;
    }}

    return true;
  }

protected:
  void flush_line(){
    size_t num = (m_xys.size()-m_pos)/2;
    if(num) {
      m_lines.push_back(line_t(m_pos,num));
    }
    m_pos = m_xys.size();
  }

  ////////////////////////////////////////////////////////
  /// outline triangles //////////////////////////////////
  ////////////////////////////////////////////////////////
  void outline_triangles_2_gl(std::ostream& a_out) {
    if(!set_char_size(a_out,m_face,height,m_scale)) return;
    FT_Pos face_height = m_face->size->metrics.height; //FT_Pos (long)

    m_tobj = gluNewTess();
    ::gluTessCallback(m_tobj,(GLUenum)GLU_TESS_BEGIN_DATA,  (Func)begin_cbk);
    ::gluTessCallback(m_tobj,(GLUenum)GLU_TESS_END_DATA,    (Func)end_cbk);
    ::gluTessCallback(m_tobj,(GLUenum)GLU_TESS_VERTEX_DATA, (Func)vertex_cbk);
    ::gluTessCallback(m_tobj,(GLUenum)GLU_TESS_COMBINE_DATA,(Func)combine_cbk);
    ::gluTessCallback(m_tobj,(GLUenum)GLU_TESS_ERROR_DATA,  (Func)error_cbk);
    ::gluTessProperty(m_tobj,(GLUenum)GLU_TESS_WINDING_RULE,
                      GLU_TESS_WINDING_ODD);

    //::gluTessProperty(m_tobj, GLU_TESS_TOLERANCE, 0);
    //::gluTessNormal(m_tobj, 0.0f, 0.0f, -1.0f);

    if(strings.values().size()) {
      float yline = 0;
      tools_vforcit(std::string,strings.values(),vit) {
        const std::string& line = *vit;
        //a_out << line << std::endl;
        m_trans_x = 0;
        m_trans_y = yline;
        unsigned int ibeg = m_triangles.size(); //for hjust.
        tools_sforcit(line,it) {
          if(!char_triangles_2_gl(a_out,*it + m_encoding_offset)) {
            ::gluDeleteTess(m_tobj);
            m_tobj = 0;  
            return;
          }  
        }  
        yline += -float(face_height)*m_scale; //height>0

       {float sx = m_trans_x;
        if(hjust==tools::sg::center) {
          unsigned int num = m_triangles.size();
          for(unsigned int index=ibeg;index<num;index++) {
            std::pair<GLUenum,triangle_t>& item = m_triangles[index];
            unsigned int pos = item.second.first;
            unsigned int npt = item.second.second;
            float* data = tools::vec_data<float>(m_xys)+pos;
            for(unsigned int i=0;i<npt;i++,data+=2) *data -= sx*0.5f;
          } 
        } else if(hjust==tools::sg::right) {
          unsigned int num = m_triangles.size();
          for(unsigned int index=ibeg;index<num;index++) {
            std::pair<GLUenum,triangle_t>& item = m_triangles[index];
            unsigned int pos = item.second.first;
            unsigned int npt = item.second.second;
            float* data = tools::vec_data<float>(m_xys)+pos;
            for(unsigned int i=0;i<npt;i++,data+=2) *data -= sx;
          } 
        }}
      }
    } else if(unitext.values().size()) {
      float yline = 0;
      tools_vforcit(uniline,unitext.values(),vit) {
        const uniline& line = *vit;
        //a_out << line << std::endl;
        m_trans_x = 0;
        m_trans_y = yline;
        unsigned int ibeg = m_triangles.size(); //for hjust.
        tools_vforcit(unichar,line,it) {
          if(!char_triangles_2_gl(a_out,*it)) {
            ::gluDeleteTess(m_tobj);
            m_tobj = 0;  
            return;
          }
        }  
        yline += -float(face_height)*m_scale; //height>0

       {float sx = m_trans_x;
        if(hjust==tools::sg::center) {
          unsigned int num = m_triangles.size();
          for(unsigned int index=ibeg;index<num;index++) {
            std::pair<GLUenum,triangle_t>& item = m_triangles[index];
            unsigned int pos = item.second.first;
            unsigned int npt = item.second.second;
            float* data = tools::vec_data<float>(m_xys)+pos;
            for(unsigned int i=0;i<npt;i++,data+=2) *data -= sx*0.5f;
          } 
        } else if(hjust==tools::sg::right) {
          unsigned int num = m_triangles.size();
          for(unsigned int index=ibeg;index<num;index++) {
            std::pair<GLUenum,triangle_t>& item = m_triangles[index];
            unsigned int pos = item.second.first;
            unsigned int npt = item.second.second;
            float* data = tools::vec_data<float>(m_xys)+pos;
            for(unsigned int i=0;i<npt;i++,data+=2) *data -= sx;
          } 
        }}
      }
    }

    ::gluDeleteTess(m_tobj);
    m_tobj = 0;  
  }

  bool char_triangles_2_gl(std::ostream& a_out,unsigned int a_unichar) {
    //if(m_verbose) {
    //  a_out << "tools::sg::text_freetype::char_triangles_2_gl :"
    //        << " do " << a_unichar << "."
    //        << std::endl;
    //}

    FT_ULong charcode = a_unichar; //charcode is UTF-32.
    FT_UInt glyph_index = ::FT_Get_Char_Index(m_face,charcode);
    //NOTE : if not found -> glyph_index = 0 which is the "missing glyph".
    if((FT_Long)glyph_index>=m_face->num_glyphs) {
      a_out << "tools::sg::text_freetype::char_triangles_2_gl :"
            << " FT_Get_Char_Index : failed for char : " << a_unichar
            << std::endl;
      ::FT_Done_Face(m_face);
      m_face = 0;
      return false;
    }
  
   {FT_Error error = ::FT_Load_Glyph(m_face,glyph_index,load_flags());
    if(error) {
      a_out << "tools::sg::text_freetype::char_triangles_2_gl :"
            << " for character " << a_unichar
            << ",FT_Load_Glyph : error : " << serror(error)
            << std::endl;
      ::FT_Done_Face(m_face);
      m_face = 0;
      return false;
    }}
  
    //FT_GlyphSlot FT_Face.glyph;
    if(m_face->glyph->format!=FT_GLYPH_FORMAT_OUTLINE) {
      a_out << "tools::sg::text_freetype::char_triangles_2_gl :"
            << " for font " << tools::sout(font.value())
            << " and for character " << a_unichar
            << " glyph not at format outline."
            << std::endl;
      ::FT_Done_Face(m_face);
      m_face = 0;
      return false;
    }
  
    FT_Outline outline = m_face->glyph->outline;

    m_glutess_trids_num = 0;
    m_combine_trids_num = 0;

    m_contour_open = false;

    ::gluTessBeginPolygon(m_tobj,this);

   {FT_Error error = ::FT_Outline_Decompose(&outline,&m_funcs,this);
    if(error) {
      a_out << "tools::sg::text_freetype::char_triangles_2_gl :"
            << " for character " << a_unichar
            << ",FT_Outline_Decompose : error : " << serror(error)
            << std::endl;
      ::FT_Done_Face(m_face);
      m_face = 0;
      return false;
    }}
  
    if(m_contour_open) {
      ::gluTessEndContour(m_tobj);
      m_contour_open = false;
    }  

    ::gluTessEndPolygon(m_tobj); //triggers callbacks and fill m_triangles.

    m_trans_x += float(m_face->glyph->advance.x)*m_scale;
    m_trans_y += float(m_face->glyph->advance.y)*m_scale;

   {wndg_type wdg =
      ((outline.flags & FT_OUTLINE_REVERSE_FILL)?wndg_ccw:wndg_cw);
    if(m_wndg==wndg_not_done) {
      m_wndg = wdg;
    } else if(m_wndg!=wdg) {
      a_out << "tools::sg::text_freetype::char_triangles_2_gl :"
            << " for character " << a_unichar << ", winding anomaly."
            << std::endl;
    }}

    return true;
  }

  bool char_2_bitmap(std::ostream& a_out,unsigned int a_unichar) {
    //if(m_verbose) {
    //  a_out << "tools::sg::text_freetype::char_2_bitmap :"
    //        << " do " << a_unichar << "."
    //        << std::endl;
    //}

    FT_ULong charcode = a_unichar; //charcode is UTF-32.
    FT_UInt glyph_index = ::FT_Get_Char_Index(m_face,charcode);
    //NOTE : if not found -> glyph_index = 0 which is the "missing glyph".
    if((FT_Long)glyph_index>=m_face->num_glyphs) {
      a_out << "tools::sg::text_freetype::char_2_bitmap :"
            << " FT_Get_Char_Index : failed for char : " << a_unichar
            << std::endl;
      ::FT_Done_Face(m_face);
      m_face = 0;
      return false;
    }
  
   {FT_Error error = ::FT_Load_Glyph(m_face,glyph_index,load_flags());
    if(error) {
      a_out << "tools::sg::text_freetype::char_2_bitmap :"
            << " for character " << a_unichar
            << ",FT_Load_Glyph : error : " << serror(error)
            << std::endl;
      ::FT_Done_Face(m_face);
      m_face = 0;
      return false;
    }}
  
    FT_Glyph glyph;
   {FT_Error error = ::FT_Get_Glyph(m_face->glyph,&glyph);
    if (error) {
      a_out << "tools::sg::text_freetype::char_2_bitmap :"
            << " for font " << tools::sout(font.value())
            << " and for character " << a_unichar
            << " could not get glyph."
            << std::endl;
      ::FT_Done_Face(m_face);
      m_face = 0;
      return false;
    }}

    bool smoothing = true;
   {FT_Error error = ::FT_Glyph_To_Bitmap(&glyph,(smoothing?ft_render_mode_normal:ft_render_mode_mono),0,1);
    if (error) {
      a_out << "tools::sg::text_freetype::char_2_bitmap :"
            << " for font " << tools::sout(font.value())
            << " and for character " << a_unichar
            << " could not get glyph bitmap."
            << std::endl;
      ::FT_Done_Glyph(glyph);
      ::FT_Done_Face(m_face);
      m_face = 0;
      return false;
    }}

    //typedef struct  FT_Bitmap_ {
    //    int             rows;
    //    int             width;
    //    int             pitch;
    //    unsigned char*  buffer;
    //    short           num_grays;
    //    char            pixel_mode;
    //    char            palette_mode;
    //   void*           palette;
    //} FT_Bitmap;

    FT_BitmapGlyph bitmap = (FT_BitmapGlyph)glyph;

    //::printf("debug : pixmap : %c : r %d  w %d p %d : grays %d\n",
    //    aChar,bitmap->bitmap.rows,bitmap->bitmap.width,bitmap->bitmap.pitch,bitmap->bitmap.num_grays);

    if( (bitmap->bitmap.pixel_mode!=ft_pixel_mode_mono) &&
        (bitmap->bitmap.pixel_mode!=ft_pixel_mode_grays) ){
      a_out << "tools::sg::text_freetype::char_2_bitmap :"
            << " for font " << tools::sout(font.value())
            << " and for character " << a_unichar
            << " not a mono or grays pixmap."
            << std::endl;
      ::FT_Done_Glyph(glyph);
      ::FT_Done_Face(m_face);
      m_face = 0;
      return false;
    } 

    if(bitmap->bitmap.pitch<0) {
      a_out << "tools::sg::text_freetype::char_2_bitmap :"
            << " for font " << tools::sout(font.value())
            << " and for character " << a_unichar
            << " negative bitmap pitch."
            << std::endl;
      ::FT_Done_Glyph(glyph);
      ::FT_Done_Face(m_face);
      m_face = 0;
      return false;
    } 

/*
    typedef struct {
      int rows;
      int cols;
      int width;
      void* buffer;
      int size;
      int grays;
    } SbTTF_Raster_Map;

    if(bitmap->bitmap.pixel_mode==ft_pixel_mode_mono) {
      a_raster.rows = bitmap->bitmap.rows;
      a_raster.width = bitmap->bitmap.width;
      //WARNING : bitmap->bitmap.pitch != int((bitmap->bitmap.width+7)/8) !!!
      // OpenGL wants the below for cols.
      a_raster.cols = (a_raster.width+7)/8;
      a_raster.size = a_raster.rows * a_raster.cols;
      a_raster.grays = 1;
    } else { //ft_pixel_mode_grays

      if(bitmap->bitmap.width!=bitmap->bitmap.pitch) {
        SoDebugError::post("SbTextTTF2::getCharacter",
          "bitmap pitch (%d) != width (%d) for character '%c'",
          aChar,bitmap->bitmap.pitch,bitmap->bitmap.width);
        FT_Done_Glyph(glyph);
        return FALSE;
      } 

      a_raster.rows = bitmap->bitmap.rows;
      a_raster.width = bitmap->bitmap.width;
      a_raster.cols = a_raster.width;
      a_raster.size = a_raster.rows * a_raster.cols;
      a_raster.grays = bitmap->bitmap.num_grays;
    }

    if(a_raster.size<=0) {
      // This may happen (for example for the space character).
      a_raster.buffer = 0;
    } else {
      a_raster.buffer = new char[a_raster.size];
      if(!a_raster.buffer) {
        SoDebugError::post("SbTextTTF2::getCharacter",
          "can't alloc bitmap buffer for character '%c'",aChar);
        FT_Done_Glyph(glyph);
        return FALSE;
      }  
      // The bitmap is upside down for OpenGL.
      for(int row=0;row<a_raster.rows;++row) {
        unsigned char* from = (unsigned char*)bitmap->bitmap.buffer + 
          (bitmap->bitmap.rows-row-1)*bitmap->bitmap.pitch;
        unsigned char* to = 
          (unsigned char*)a_raster.buffer + row * a_raster.cols;
        for( int col=0;col<a_raster.cols;++col) to[col] = from[col];
      }
    }

    FT_BBox bbox;
    FT_Glyph_Get_CBox(glyph,ft_glyph_bbox_pixels,&bbox);

    aBox.xMin = int(bbox.xMin);
    aBox.yMin = int(bbox.yMin);
    aBox.xMax = int(bbox.xMax);
    aBox.yMax = int(bbox.yMax);
    aAdvance = int(face->glyph->advance.x/64);
  */

    ::FT_Done_Glyph(glyph);

    /*
    if(bitmap->bitmap.pixel_mode==ft_pixel_mode_mono) {
      if((aChar=='T')||(aChar=='a')||(aChar=='o')||(aChar=='L')) {
        printf("bitmap for '%c' : w = %d h = %d cols %d : ptsize = %d\n",
          aChar,a_raster.width,a_raster.rows,a_raster.cols,getPointSize());
        for( int row = (a_raster.rows-1); row >=0; --row ) {
          unsigned char* bline = (unsigned char*)a_raster.buffer + row * a_raster.cols;
          int icol = 0;
          int ibit = 0;
          unsigned char byte = (unsigned char)bline[icol];
          icol++;
          for( int i= 0; i < a_raster.width; ++i ) {
            unsigned char v =  (byte & (1<<(7-ibit)));
            printf("%c",(v?'x':' '));
            ibit++;
            if(ibit==8) {
              ibit = 0;
              byte = (unsigned char)bline[icol];
              icol++;
            }
          }
          printf("\n");
        }
      }
    }*/

    return true;
  }

  typedef _GLUfuncptr Func;

  static void GLUAPIENTRY begin_cbk(GLUenum a_which,void* a_this) {
    text_freetype& self = *((text_freetype*)a_this);
    self.m_mode = a_which;
    self.m_pos = self.m_xys.size();
#ifdef TOOLS_SG_TEXT_FREETYPE_DEBUG
    self.m_out << "tools::sg::text_freetype::begin_cbk :"
          << " which " << a_which
          << " GL_TRIANGLE_STRIP " << GL_TRIANGLE_STRIP
          << " GL_TRIANGLE_FAN " << GL_TRIANGLE_FAN
          << " GL_TRIANGLES " << GL_TRIANGLES
          << std::endl;
#endif
  }

  static void GLUAPIENTRY vertex_cbk(void* a_vertex,void* a_this) {
    text_freetype& self = *((text_freetype*)a_this);
    double* vertex = (double*)a_vertex;
#ifdef TOOLS_SG_TEXT_FREETYPE_DEBUG
    std::cout << "tools::sg::text_freetype::vertex_cbk :"
          << " x " << vertex[0]
          << " y " << vertex[1]
          << " z " << vertex[2]
          << std::endl;
#endif
    self.add_xy(float(vertex[0]),float(vertex[1]));
  }

  static void GLUAPIENTRY end_cbk(void* a_this){
    text_freetype& self = *((text_freetype*)a_this);
    size_t num = (self.m_xys.size()-self.m_pos)/2;
    if(num) {
      triangle_t t(self.m_pos,num);
      self.m_triangles.push_back(std::pair<GLUenum,triangle_t>(self.m_mode,t));
    }
  }

  static void GLUAPIENTRY combine_cbk(double a_coords[3],
              void* /*a_vertex_data*/[4],
              float /*a_weight*/[4],
              void** a_data_out,
              void* a_this) {
    text_freetype& self = *((text_freetype*)a_this);
    double* v = self.add_combine_vec3d(a_coords[0],a_coords[1],a_coords[2]);
    //if(!v) ???
    *a_data_out = v;
  }

  static void GLUAPIENTRY error_cbk(GLUenum,void*) {
    //const GLubyte* estring = gluErrorString(aErrorCode);
    //::fprintf(stderr, "Tessellation Error: %s\n", estring);
    //SbTessContour* This = (SbTessContour*)aThis;
    //This->setError(true);
  }

  ////////////////////////////////////////////////////////
  /// outline triangles : end ////////////////////////////
  ////////////////////////////////////////////////////////

  ////////////////////////////////////////////////////////
  /// FT_Outline_Decompose callbacks /////////////////////
  ////////////////////////////////////////////////////////
  static int outline_move_to(const FT_Vector* a_to,void* a_this){
    // NOTE : get x coords in units of wchar,
    //        get y coords in units of hchar.
    //        Exa : if char_width is 100*64 get some x of
    //        the same magnitude (in [0,6400]).
    text_freetype& self = *((text_freetype*)a_this);

    //self.m_out << "tools::sg::text_freetype::outline_move_to :"
    //           << " x " << a_to->x
    //           << " y " << a_to->y
    //           << std::endl;

    float gx,gy;
    self.set_g(gx,gy,float(a_to->x),float(a_to->y));

    if(self.m_tobj) {
      if(self.m_contour_open) {
        ::gluTessEndContour(self.m_tobj);
        self.m_contour_open = false;
      }

      ::gluTessBeginContour(self.m_tobj);
      self.m_contour_open = true;

     {double* v = self.add_glutess_vec3d(gx,gy,0);
      ::gluTessVertex(self.m_tobj,v,v);}

    } else {
      self.flush_line();
      self.add_xy(gx,gy);
    }

    self.m_last_x = float(a_to->x);
    self.m_last_y = float(a_to->y);

    return 0;
  }
  static int outline_line_to(const FT_Vector* a_to,void* a_this){
    text_freetype& self = *((text_freetype*)a_this);

    //self.m_out << "tools::sg::text_freetype::outline_line_to :"
    //           << " x " << a_to->x
    //           << " y " << a_to->y
    //           << std::endl;

    float gx,gy;
    self.set_g(gx,gy,float(a_to->x),float(a_to->y));

    if(self.m_tobj) {      
      double* v = self.add_glutess_vec3d(gx,gy,0);
      ::gluTessVertex(self.m_tobj,v,v);
    } else {
      self.add_xy(gx,gy);
    }

    self.m_last_x = float(a_to->x);
    self.m_last_y = float(a_to->y);

    return 0;
  }
  static int outline_conic_to(const FT_Vector* a_ctrl,const FT_Vector* a_to,void* a_this){
    text_freetype& self = *((text_freetype*)a_this);

    // it must be fast. We avoid vec3f manipulations.

    //self.m_out << "tools::sg::text_freetype::outline_conic_to :"
    //           << " ctrl x " << a_ctrl->x
    //           << " ctrl y " << a_ctrl->y
    //           << " x " << a_to->x
    //           << " y " << a_to->y
    //           << std::endl;

    float ctrlx = float(a_ctrl->x);
    float ctrly = float(a_ctrl->y);

    float fromx = self.m_last_x;
    float fromy = self.m_last_y;

    float tox = float(a_to->x);
    float toy = float(a_to->y);

    // logic taken from OGLFT.

    //OPTIMIZE :
    //b = from - 2 * ctrl + to;
    //c = -2 * from + 2 * ctrl;
    //df = c * self.m_delta + b * self.m_delta2;
    //df2 = 2 * b * self.m_delta2;
    float bx = fromx - 2 * ctrlx + tox;
    float by = fromy - 2 * ctrly + toy;

    float cx = -2 * fromx + 2 * ctrlx;
    float cy = -2 * fromy + 2 * ctrly;

    float dfx = cx * self.m_delta + bx * self.m_delta2;
    float dfy = cy * self.m_delta + by * self.m_delta2;

    float df2x = 2 * bx * self.m_delta2;
    float df2y = 2 * by * self.m_delta2;

    // if steps = 4, num = 3
    //   from (i=0) (i=1) (i=2) (to)
    // then we have four steps between [from,to]

    // from = starting point.

    float gx,gy;

    unsigned int num = self.m_steps - 1;
    for(unsigned int i=0;i<num;i++) {
      fromx += dfx;
      fromy += dfy;

      self.set_g(gx,gy,fromx,fromy);

      if(self.m_tobj) {
        double* v = self.add_glutess_vec3d(gx,gy,0);
        ::gluTessVertex(self.m_tobj,v,v);
      } else {
        self.add_xy(gx,gy);
      }

      dfx += df2x;
      dfy += df2y;
    }

    //g = to;
    self.set_g(gx,gy,tox,toy);

    if(self.m_tobj) {
      double* v = self.add_glutess_vec3d(gx,gy,0);
      ::gluTessVertex(self.m_tobj,v,v);
    } else {
      self.add_xy(gx,gy);
    }

    //self.m_last = to;
    self.m_last_x = tox;
    self.m_last_y = toy;

    return 0;
  }
  void set_g(float& a_gx,float& a_gy,
             float a_x,float a_y) const {
    a_gx = a_x*m_scale+m_trans_x;
    a_gy = a_y*m_scale+m_trans_y;
  }
  static int outline_cubic_to(const FT_Vector* a_ctrl1,const FT_Vector* a_ctrl2,const FT_Vector* a_to,void* a_this){
    text_freetype& self = *((text_freetype*)a_this);

    // it must be fast. We avoid vec3f manipulations.

    //self.m_out << "tools::sg::text_freetype::outline_cubic_to :"
    //      << " ctrl1 x " << a_ctrl1->x
    //      << " ctrl1 y " << a_ctrl1->y
    //      << " ctrl2 x " << a_ctrl2->x
    //      << " ctrl2 y " << a_ctrl2->y
    //      << " x " << a_to->x
    //      << " y " << a_to->y
    //      << std::endl;

    float ctrl1x = float(a_ctrl1->x);
    float ctrl1y = float(a_ctrl1->y);

    float ctrl2x = float(a_ctrl2->x);
    float ctrl2y = float(a_ctrl2->y);

    float fromx = self.m_last_x;
    float fromy = self.m_last_y;

    float tox = float(a_to->x);
    float toy = float(a_to->y);

    // logic taken from OGLFT.

    //OPTIMIZE :
    //a = -from + 3 * ctrl1 - 3 * ctrl2 + to;  
    //b = 3 * from - 6 * ctrl1 + 3 * ctrl2;
    //c = -3 * from + 3 * ctrl1;
    //df = c * self.m_delta + b * self.m_delta2 + a * self.m_delta3;
    //df2 = 2 * b * self.m_delta2 + 6 * a * self.m_delta3;
    //df3 = 6 * a * self.m_delta3;

    float ax = -fromx + 3 * ctrl1x - 3 * ctrl2x + tox;
    float ay = -fromy + 3 * ctrl1y - 3 * ctrl2y + toy;

    float bx = 3 * fromx - 6 * ctrl1x + 3 * ctrl2x;
    float by = 3 * fromy - 6 * ctrl1y + 3 * ctrl2y;

    float cx = -3 * fromx + 3 * ctrl1x;
    float cy = -3 * fromy + 3 * ctrl1y;

    float dfx = cx * self.m_delta + bx * self.m_delta2 + ax * self.m_delta3;
    float dfy = cy * self.m_delta + by * self.m_delta2 + ay * self.m_delta3;

    float df2x = 2 * bx * self.m_delta2 + 6 * ax * self.m_delta3;
    float df2y = 2 * by * self.m_delta2 + 6 * ay * self.m_delta3;

    float df3x = 6 * ax * self.m_delta3;
    float df3y = 6 * ay * self.m_delta3;

    // if steps = 4, num = 3
    //   from (i=0) (i=1) (i=2) (to)
    // then we have four steps between [from,to]

    // from = starting point.
    float gx,gy;

    unsigned int num = self.m_steps - 1;
    for(unsigned int i=0;i<num;i++) {
      fromx += dfx;
      fromy += dfy;

      self.set_g(gx,gy,fromx,fromy);

      if(self.m_tobj) {
        double* v = self.add_glutess_vec3d(gx,gy,0);
        ::gluTessVertex(self.m_tobj,v,v);
      } else {
        self.add_xy(gx,gy);
      }

      dfx += df2x;
      dfy += df2y;

      df2x += df3x;
      df2y += df3y;
    }

    //g = to;
    self.set_g(gx,gy,tox,toy);

    if(self.m_tobj) {
      double* v = self.add_glutess_vec3d(gx,gy,0);
      ::gluTessVertex(self.m_tobj,v,v);
    } else {
      self.add_xy(gx,gy);
    }  

    //self.m_last = to;
    self.m_last_x = tox;
    self.m_last_y = toy;

    return 0;
  }


public:
#ifndef SWIG
  static std::string serror(int a_FT_Error) {    
    static std::map<int,std::string> errs;
    if(errs.empty()) {
#undef __FTERRORS_H__                                           
#define FT_ERROR_START_LIST
#define FT_ERROR_END_LIST
#define FT_ERRORDEF( e, v, s )  errs[e] = s;
#include FT_ERRORS_H
    }
    std::map<int,std::string>::const_iterator it = errs.find(a_FT_Error);
    if(it!=errs.end()) return (*it).second;
    return "unknown";
  }
#endif
protected:
  void add_xy(float a_x,float a_y) {
    m_xys.push_back(a_x);
    m_xys.push_back(a_y);
  }

  double* add_glutess_vec3d(float a_x,float a_y,float a_z) {
    double* v = 0;
    if(m_glutess_trids_num>=m_glutess_trids.size()) {
      v = new double[3];
      m_glutess_trids.push_back(v);
    } else {
      v = m_glutess_trids[m_glutess_trids_num];
    }
    m_glutess_trids_num++;

    v[0] = a_x;
    v[1] = a_y;
    v[2] = a_z;

    return v;
  }

  double* add_combine_vec3d(double a_x,double a_y,double a_z) {
    double* v = 0;
    if(m_combine_trids_num>=m_combine_trids.size()) {
      v = new double[3];
      m_combine_trids.push_back(v);
    } else {
      v = m_combine_trids[m_combine_trids_num];
    }
    m_combine_trids_num++;

    v[0] = a_x;
    v[1] = a_y;
    v[2] = a_z;

    return v;
  }

  void clear_trids() {
   {tools_vforit(double*,m_glutess_trids,it) delete [] *it;
    m_glutess_trids.clear();}

   {tools_vforit(double*,m_combine_trids,it) delete [] *it;
    m_combine_trids.clear();}
  }

  void load_face(std::ostream& a_out) {
    if(!m_library) {
      a_out << "tools::sg::text_freetype::load_face :"
            << " freetype library not initialized."
            << std::endl;
      return;
    }

    if(m_verbose) {
      a_out << "tools::sg::text_freetype::load_face :"
            << " font is " << tools::sout(font.value()) << "."
            << std::endl;
    }

    if(m_face) {
      ::FT_Done_Face(m_face);
      m_face = 0;
    }
    if(font.value().empty()) {
      a_out << "tools::sg::text_freetype::load_face :"
            << " no font file given."
            << std::endl;
      return;
    }
    std::string file;
    if(!tools::find_with_path(a_out,"TOOLS_FONT_PATH",font.value(),file)) {
      a_out << "tools::sg::text_freetype::load_face :"
            << " font file not found for font " 
            << tools::sout(font.value()) << "."
            << std::endl;
      return;
    }

    if(m_verbose) {
      a_out << "tools::sg::text_freetype::load_face :"
            << " load font file " << tools::sout(file) << " ..."
            << std::endl;
    }

    FT_Error error = ::FT_New_Face(m_library,file.c_str(),0,&m_face);
    if(error) { 
      a_out << "tools::sg::text_freetype::load_face :"
            << " FT_New_Face : error : " << serror(error) << "."
            << " for font file " << tools::sout(file) << "."
            << std::endl;
      m_face = 0;
      return;
    }

    if(m_verbose) {
      a_out << "tools::sg::text_freetype::load_face :"
            << " load ok."
            << std::endl;
    }

  }

protected:
  static int load_flags() {
    return FT_LOAD_DEFAULT | FT_LOAD_NO_HINTING;
    //return FT_LOAD_DEFAULT;
    //return FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP;
  }

  static bool set_char_size(std::ostream& a_out,FT_Face& a_face,
                            float a_height,float& a_scale) {

    // arrange char_width, char_height and m_scale so
    // that text height be 1 in world coordinates.

    // What these fancy 26.6, 64, 72 number mean ?
    // In what unit do we receive points in callbacks ?
    // Knowing char_height and vres can we know height of points ?

/*
*/
    FT_F26Dot6 wchar = 1000*64;
    FT_F26Dot6 hchar = 1000*64;
    FT_UInt hres = 72;
    FT_UInt vres = 72;

/*
    FT_F26Dot6 wchar = 1000;
    FT_F26Dot6 hchar = 1000;
    FT_UInt hres = 100;
    FT_UInt vres = 100;
*/

    FT_Error error = ::FT_Set_Char_Size(a_face,wchar,hchar,hres,vres);
    if(error) {
      a_out << "tools::sg::text_freetype::set_char_size :"
            << " FT_Set_Char_Size : error : " << serror(error) << "."
            << std::endl;
      ::FT_Done_Face(a_face);
      a_face = 0;
      a_scale = 1;
      return false;
    }
    a_scale = a_height/float(wchar);
    return true;
  }

  static bool ascent(std::ostream& a_out,FT_Face a_face,float a_height,
                     float& a_value) {
    float scale;
    if(!set_char_size(a_out,a_face,a_height,scale)) {a_value = 0;return false;}
    FT_Pos ascent = a_face->size->metrics.ascender; //FT_Pos (long)
    a_value = float(ascent) * scale;
    return true;
  }

  static bool descent(std::ostream& a_out,FT_Face a_face,float a_height,
                      float& a_value) {
    float scale;
    if(!set_char_size(a_out,a_face,a_height,scale)) {a_value = 0;return false;}
    FT_Pos descent = a_face->size->metrics.descender; //FT_Pos (long) //<0
    a_value = -(float(descent) * scale);
    return true;
  }

  static bool y_advance(std::ostream& a_out,FT_Face a_face,float a_height,
                        float& a_adv) {
    float scale;
    if(!set_char_size(a_out,a_face,a_height,scale)) {a_adv = 0;return false;}
    FT_Pos face_height = a_face->size->metrics.height; //FT_Pos (long)
    a_adv = float(face_height)*scale;
    return true;
  }

  static bool truncate(std::ostream& a_out,FT_Face a_face,float a_height,
                       const std::string& a_string,
                       float a_cut_width,std::string& a_sout) {
    a_sout.clear();

    float scale;
    if(!set_char_size(a_out,a_face,a_height,scale)) return false;

    float width = 0;

    unsigned short offset;
    if(!encoding_offset(a_face,offset)) return false;

    tools_sforcit(a_string,it) {
      FT_ULong charcode = *it + offset; //charcode is UTF-32.
      FT_UInt glyph_index = ::FT_Get_Char_Index(a_face,charcode);
      //NOTE : if not found -> glyph_index = 0 which is the "missing glyph".
      if((FT_Long)glyph_index>=a_face->num_glyphs) {
#ifdef TOOLS_SG_TEXT_FREETYPE_DEBUG
        m_out << "tools::sg::text_freetype::truncate :"
              << " FT_Get_Char_Index : failed for char : " << *it 
              << std::endl;
#endif
        a_sout.clear();
        return false;
      }
  
     {FT_Error error = ::FT_Load_Glyph(a_face,glyph_index,load_flags());
      if(error) {
#ifdef TOOLS_SG_TEXT_FREETYPE_DEBUG
        m_out << "tools::sg::text_freetype::truncate :"
              << " for character " << *it
              << ",FT_Load_Glyph : error : " << serror(error)
              << std::endl;
#endif
        a_sout.clear();
        return false;
      }}
  
      float cwidth = float(a_face->glyph->metrics.width)*scale;
      float advance = float(a_face->glyph->advance.x)*scale;
      if((width+cwidth)>=a_cut_width) return true;
      a_sout += *it;
      width += advance;
    }

    return true;
  }

  static bool get_bounds(std::ostream& a_out,FT_Face a_face,float a_height,
                                const std::vector<std::string>& a_text,
                                float& a_mn_x,float& a_mn_y,float& a_mn_z,
                                float& a_mx_x,float& a_mx_y,float& a_mx_z){
    tools::box_3f_make_empty(a_mn_x,a_mn_y,a_mn_z,a_mx_x,a_mx_y,a_mx_z);

    if(a_text.empty()) return true;

    float scale;
    if(!set_char_size(a_out,a_face,a_height,scale)) return false;

    float xmx = 0;

    unsigned short offset;
    if(!encoding_offset(a_face,offset)) return false;

    tools_vforcit(std::string,a_text,vit) {
      const std::string& line = *vit;

      float width = 0;
      tools_sforcit(line,it) {
        FT_ULong charcode = *it + offset; //charcode is UTF-32.
        FT_UInt glyph_index = ::FT_Get_Char_Index(a_face,charcode);
        //NOTE : if not found -> glyph_index = 0 which is the "missing glyph".
        if((FT_Long)glyph_index>=a_face->num_glyphs) {
          return false;
        }
    
       {FT_Error error = ::FT_Load_Glyph(a_face,glyph_index,load_flags());
        if(error) {
          return false;
        }}
    
        //float cwidth = float(a_face->glyph->metrics.width)*scale;
        float advance = float(a_face->glyph->advance.x)*scale;
        width += advance;
      }

      xmx = tools::mx<float>(xmx,width);
    }

    FT_Pos ascent = a_face->size->metrics.ascender; //FT_Pos (long)
    FT_Pos descent = a_face->size->metrics.descender; //FT_Pos (long) //<0
    FT_Pos face_height = a_face->size->metrics.height; //FT_Pos (long)
  
    float ymn = -float(face_height)*scale*(a_text.size()-1) //height>0
                +float(descent)*scale;

    a_mn_x = 0;
    a_mn_y = ymn;
    a_mn_z = 0;
    a_mx_x = xmx;
    a_mx_y = float(ascent)*scale;
    a_mx_z = 0;
  
    return true;
  }

  static bool get_bounds(std::ostream& a_out,FT_Face a_face,float a_height,
                                const std::vector<uniline>& a_text,
                                float& a_mn_x,float& a_mn_y,float& a_mn_z,
                                float& a_mx_x,float& a_mx_y,float& a_mx_z){
    tools::box_3f_make_empty(a_mn_x,a_mn_y,a_mn_z,a_mx_x,a_mx_y,a_mx_z);

    if(a_text.empty()) return true;

    float scale;
    if(!set_char_size(a_out,a_face,a_height,scale)) return false;

    float xmx = 0;

    tools_vforcit(uniline,a_text,vit) {
      const uniline& line = *vit;
      float width = 0;

      tools_vforcit(unichar,line,it) {

        FT_ULong charcode = *it; //charcode is UTF-32.
        FT_UInt glyph_index = ::FT_Get_Char_Index(a_face,charcode);
        //NOTE : if not found -> glyph_index = 0 which is the "missing glyph".
        if((FT_Long)glyph_index>=a_face->num_glyphs) {
          return false;
        }
    
       {FT_Error error = ::FT_Load_Glyph(a_face,glyph_index,load_flags());
        if(error) {
          return false;
        }}
    
        //float cwidth = float(a_face->glyph->metrics.width)*scale;
        float advance = float(a_face->glyph->advance.x)*scale;
        width += advance;
      }

      xmx = tools::mx<float>(xmx,width);
    }

    FT_Pos ascent = a_face->size->metrics.ascender; //FT_Pos (long)
    FT_Pos descent = a_face->size->metrics.descender; //FT_Pos (long) //<0
    FT_Pos face_height = a_face->size->metrics.height; //FT_Pos (long)
  
    float ymn = -float(face_height)*scale*(a_text.size()-1) //height>0
                +float(descent)*scale;

    a_mn_x = 0;
    a_mn_y = ymn;
    a_mn_z = 0;
    a_mx_x = xmx;
    a_mx_y = float(ascent)*scale;
    a_mx_z = 0;
  
    return true;
  }

  static bool encoding_offset(FT_Face a_face,unsigned short& a_offset) {

    // arialbd.ttf :
    //   num charmap 2
    //     charmap 0, platform 1, encoding 0.
    //     charmap 1, platform 3, encoding 1.

    // symbol.ttf :
    //   num charmap 2
    //     charmap 0, platform 1, encoding 0.
    //     charmap 1, platform 3, encoding 0.

    // stixgeneral.otf :
    //   num charmap 6.
    //   charmap 0, platform 0, encoding 3.
    //   charmap 1, platform 0, encoding 4.
    //   charmap 2, platform 1, encoding 0.
    //   charmap 3, platform 3, encoding 1.

    a_offset = 0;

    //std::cout << "tools::sg::text_freetype::encoding_offset :"
    //          << " num charmap " << a_face->num_charmaps  << "."
    //          << std::endl;

    // cooking to handle symbol.ttf and wingding.ttf :
    FT_Int n = a_face->num_charmaps;
    FT_Int i;
    for ( i = 0; i < n; i++ ) {
      FT_CharMap charmap = a_face->charmaps[i];
      unsigned short platform = charmap->platform_id;
      unsigned short encoding = charmap->encoding_id;

      //std::cout << "tools::sg::text_freetype::encoding_offset :"
      //          << " for charmap " << i
      //          << ", platform " << platform
      //          << ", encoding " << encoding << "."
      //          << std::endl;

      if ( (platform == 3 && encoding == 1 )  ||
           (platform == 3 && encoding == 0 )  ||
           //(platform == 1 && encoding == 0 )  ||
           (platform == 0 && encoding == 0 ) ) {
          FT_Error error = FT_Set_Charmap(a_face,charmap);
          if(error) {
            a_offset = 0;
            return false;
          }
          // For symbol.ttf and wingding.ttf
          if (platform == 3 && encoding == 0 ) a_offset = 0xF000;
          //if (platform == 1 && encoding == 0 ) a_offset = 0xF000;
          return true;

      } else {
        //SoDebugError::post("SbTextTTF2Face::loadFont",
        //  "for \"%s\", platform %d and encoding %d not taken into account",
        //  filename,platform,encoding);
      }
    }

    //a_out << "tools::sg::text_freetype::update_sg :"
    //      << " This font doesn't contain any Unicode mapping table."
    //      << std::endl;
    a_offset = 0;
    return false;
  }

protected:
  FT_Library m_library;
  FT_Face m_face;
  unsigned short m_encoding_offset;
  bool m_verbose; //append _ to avoid clash with inlib/sg/guib::m_verbose
  ////////////////////////////////////////////////////////
  /// outline ////////////////////////////////////////////
  ////////////////////////////////////////////////////////
  FT_Outline_Funcs m_funcs; //See doc in ftimage.h
  float m_last_x,m_last_y;
  float m_scale;
  float m_trans_x,m_trans_y;
  unsigned int m_steps;
  float m_delta;
  float m_delta2;
  float m_delta3;

  std::vector<float> m_xys;
  ////////////////////////////////////////////////////////
  /// outline lines //////////////////////////////////////
  ////////////////////////////////////////////////////////
  typedef std::pair<unsigned int,unsigned int> line_t; //pos in m_xys.
  typedef std::vector<line_t> lines_t;
  lines_t m_lines;
  ////////////////////////////////////////////////////////
  /// outline triangles //////////////////////////////////
  ////////////////////////////////////////////////////////
  GLUtesselator* m_tobj;
  bool m_contour_open;
  std::vector<double*> m_glutess_trids;
  unsigned int m_glutess_trids_num;
  std::vector<double*> m_combine_trids;
  unsigned int m_combine_trids_num;
  GLUenum m_mode;
  typedef std::pair<unsigned int,unsigned int> triangle_t; //pos in m_xys.
  typedef std::pair<GLUenum,triangle_t> gl_triangle_t;
  typedef std::vector<gl_triangle_t> triangles_t;
  triangles_t m_triangles;
  size_t m_pos;
  unsigned int m_gsto_lines_sz;
  unsigned int m_gsto_sz;
  wndg_type m_wndg;

};

}}

//exlib_build_use inlib freetype inlib_glutess

#endif
