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

#ifndef toolx_sg_text_freetype
#define toolx_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/file>
#include <tools/nostream>
#include <tools/lina/box_3f>

// font_pixmap :
#include <tools/sg/group>
#include <tools/sg/blend>
#include <tools/sg/tex_quadrilateral>

#include <tools/glutess/glutess>

#include <map> //for errors

#ifndef SWIG
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
#include FT_OUTLINE_H
#endif

namespace toolx {
namespace sg {

class text_freetype : public tools::sg::base_freetype {
  TOOLS_NODE(text_freetype,toolx::sg::text_freetype,tools::sg::base_freetype)
protected:
  text_freetype& self() const {return const_cast<text_freetype&>(*this);}
protected:
  TOOLS_CLASS_STRING(TOOLS_FONT_PATH)
protected:
  enum wndg_type {
    wndg_ccw,
    wndg_cw,
    wndg_not_done
  };
protected: //gstos
  virtual unsigned int create_gsto(std::ostream& a_out,tools::sg::render_manager& a_mgr) {
    std::vector<float> gsto_data;

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

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

      const float* data = m_xys.data()+pos;

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

      size_t nsegs = num-1;

      size_t ngsto = nsegs*2*3; //3 = have a z (Windows GL).
      size_t sz = gsto_data.size();
      gsto_data.resize(sz+ngsto);
      float* pxyz = gsto_data.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;
      size_t pos = item.second.first;
      size_t num = item.second.second;

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

      const float* data = m_xys.data()+pos;

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

        size_t ntri = num/3;

        size_t ngsto = ntri*3*3;
        size_t sz = gsto_data.size();
        gsto_data.resize(sz+ngsto);
        float* pxyz = gsto_data.data()+sz;

        tools::gl::cvt_2to3(num,data,pxyz);

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

        size_t ntri = num-2;

        size_t ngsto = ntri*3*3;
        size_t sz = gsto_data.size();
        gsto_data.resize(sz+ngsto);
        float* pxyz = gsto_data.data()+sz;

        tools::gl::triangle_fan_to_triangles_2to3(num,data,pxyz);

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

        size_t ntri = num-2;

        size_t ngsto = ntri*3*3;
        size_t sz = gsto_data.size();
        gsto_data.resize(sz+ngsto);
        float* pxyz = gsto_data.data()+sz;

        tools::gl::triangle_strip_to_triangles_2to3(num,data,pxyz);

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

    }}

    m_gsto_sz = gsto_data.size();

    if(gsto_data.empty()) {
      return 0;
    }

    return a_mgr.create_gsto_from_data(gsto_data);
  }

public:
  virtual void render(tools::sg::render_action& a_action) {
   {bool _color_touched = color_touched(a_action.state());
    bool _char_height_touched = char_height_touched(a_action.state());
    if(touched()||_color_touched||_char_height_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.render_manager());
      if(_id) {
        a_action.begin_gsto(_id);
        size_t 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.render_manager());
    }

    // immediate rendering :

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

    a_action.set_line_smooth(true);

   {tools_vforcit(line_t,m_lines,it) {
      const line_t& item = *it;
      size_t pos = item.first;
      size_t num = item.second;
      if(!num) continue;

      const float* data = m_xys.data()+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;
      size_t pos = item.second.first;
      size_t num = item.second.second;
      if(!num) continue;

      const float* data = m_xys.data()+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);

    ///////////////////////////////////////////////////////////
    /// bitmap ////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////
    m_bitmaps.render(a_action);
  }

  virtual void pick(tools::sg::pick_action& a_action) {
   {bool _char_height_touched = char_height_touched(a_action.state());
    if(touched()||_char_height_touched) {
      update_sg(a_action.out(),font.touched());
      reset_touched();
    }}

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

   {tools_vforcit(line_t,m_lines,it) {
      const line_t& item = *it;
      size_t pos = item.first;
      size_t num = item.second;
      const float* data = m_xys.data()+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;
      size_t pos = item.second.first;
      size_t num = item.second.second;
      const float* data = m_xys.data()+pos;
      if(a_action.add__primitive_xy(*this,item.first,2*num,data,true)) return;
    }}

    ///////////////////////////////////////////////////////////
    /// bitmap ////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////
    m_bitmaps.pick(a_action);
  }

  virtual void bbox(tools::sg::bbox_action& a_action) {
   {bool _char_height_touched = char_height_touched(a_action.state());
    if(touched()||_char_height_touched) {
      update_sg(a_action.out(),font.touched());
      reset_touched();
    }}

   {tools_vforcit(line_t,m_lines,it) {
      const line_t& item = *it;
      size_t num = item.second;
      const float* data = m_xys.data()+item.first;

      float px,py,pz;
      float* pos = (float*)data;
      for(size_t 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;
      size_t num = item.second.second;
      const float* data = m_xys.data()+item.second.first;

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

    ///////////////////////////////////////////////////////////
    /// bitmap ////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////
    m_bitmaps.bbox(a_action);
  }

public:
  text_freetype()
  :parent()

  ,m_library(0)
  ,m_face(0)
  ,m_encoding_offset(0)
  ,m_verbose(false)
  ,m_scale(1)
  ,m_pos(0)
  ,m_char_height(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)
  ,m_char_height(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;
    m_char_height = 0;

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

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

  virtual float descent(float a_height) const {
    tools::nostream out;
    if(!m_face) self().load_face(out);
    if(!m_face) return 0;
    float value;
    if(!descent(out,self().m_face,a_height,value)) return 0;
    return value;
  }

  virtual float y_advance(float a_height) const {
    tools::nostream out;
    if(!m_face) self().load_face(out);
    if(!m_face) return 0;
    float value;
    if(!y_advance(out,self().m_face,a_height,value)) return 0;
    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) self().load_face(out);
    if(!m_face) return;
    if(strings.values().size()) {
      if(!get_bounds(out,self().m_face,a_height,strings.values(),a_mn_x,a_mn_y,a_mn_z,a_mx_x,a_mx_y,a_mx_z)) return;
    } else if(unitext.values().size()) {
      if(!get_bounds(out,self().m_face,a_height,unitext.values(),a_mn_x,a_mn_y,a_mn_z,a_mx_x,a_mx_y,a_mx_z)) return;
    }
  }

  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) self().load_face(out);
    if(!m_face) return false;
    if(!truncate(out,self().m_face,a_height,a_string,a_cut_width,a_out)) return false;
    return true;
  }

  void dump_unitext(std::ostream& a_out) {
    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;
      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_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
  };

  bool color_touched(const tools::sg::state& a_state) {
    if(modeling!=tools::sg::font_pixmap) return false;
    if(a_state.m_color==m_front_color) return false;
    m_front_color = a_state.m_color;
    return true;
  }

  bool char_height_touched(const tools::sg::state& a_state) {
    if(modeling!=tools::sg::font_pixmap) return false;

    float ymn,ymx;
   {float x,y,z,w;
    x = 0;y = -height.value()*0.5f;z = 0;
    if(!a_state.project_point(x,y,z,w)) return false;
    ymn = y;
    x = 0;y = height.value()*0.5f;z = 0;
    if(!a_state.project_point(x,y,z,w)) return false;
    ymx = y;}
    float screen_height = ymx-ymn;
    if(a_state.m_wh) screen_height *= a_state.m_wh; else screen_height = 100;

    if(screen_height==m_char_height) return false;
    m_char_height = screen_height;
    return true;
  }

  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 render_manager.

    if(!m_face) return;

    m_encoding_offset = 0;
    if(!encoding_offset(m_face,m_encoding_offset)) {
      a_out << "toolx::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_bitmaps.clear();
    m_tqs.clear();

    m_wndg = wndg_not_done;

    if(modeling==tools::sg::font_pixmap) {
      if(m_char_height<=0) return;
      tools::sg::blend* blend = new tools::sg::blend;
      blend->on = true; //to handle background transparency.
      m_bitmaps.add(blend);
      if(!bitmap_2_gl(a_out)) {m_bitmaps.clear();m_tqs.clear();return;}
    } else {
      update_what _what = 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;
        size_t pos = item.first;
        size_t npt = item.second;
        float* data = m_xys.data()+pos+1;
        for(size_t 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;
        size_t pos = item.second.first;
        size_t npt = item.second.second;
        float* data = m_xys.data()+pos+1;
        for(size_t i=0;i<npt;i++,data+=2) *data -= 0.5F * szy;
      }}

     {tools_vforcit(tools::sg::tex_quadrilateral*,m_tqs,itqs) {
        std::vector<tools::vec3f>& vcs = (*itqs)->corners.values();
        for(size_t i=0;i<4;i++) vcs[i][1] -= 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;
        size_t pos = item.first;
        size_t npt = item.second;
        float* data = m_xys.data()+pos+1;
        for(size_t i=0;i<npt;i++,data+=2) *data -= szy;
      }}

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

     {tools_vforcit(tools::sg::tex_quadrilateral*,m_tqs,itqs) {
        std::vector<tools::vec3f>& vcs = (*itqs)->corners.values();
        for(size_t i=0;i<4;i++) vcs[i][1] -= szy;
      }}

    }

    m_tqs.clear();
  }

  void outline_lines_2_gl(std::ostream& a_out) {
    if(!set_char_size(a_out,m_face,height.value(),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;
        size_t ibeg = m_lines.size(); //for hjust.
        for(auto it = line.cbegin(); it != line.cend(); ++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) {
          size_t num = m_lines.size();
          for(size_t index=ibeg;index<num;index++) {
            line_t& item = m_lines[index];
            size_t pos = item.first;
            size_t npt = item.second;
            float* data = m_xys.data()+pos;
            for(size_t i=0;i<npt;i++,data+=2) *data -= 0.5F * sx;
          }
        } else if(hjust==tools::sg::right) {
          size_t num = m_lines.size();
          for(size_t index=ibeg;index<num;index++) {
            line_t& item = m_lines[index];
            size_t pos = item.first;
            size_t npt = item.second;
            float* data = m_xys.data()+pos;
            for(size_t 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;
        m_trans_x = 0;
        m_trans_y = yline;
        size_t 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) {
          size_t num = m_lines.size();
          for(size_t index=ibeg;index<num;index++) {
            line_t& item = m_lines[index];
            size_t pos = item.first;
            size_t npt = item.second;
            float* data = m_xys.data()+pos;
            for(size_t i=0;i<npt;i++,data+=2) *data -= 0.5F*sx;
          }
        } else if(hjust==tools::sg::center) {
          size_t num = m_lines.size();
          for(size_t index=ibeg;index<num;index++) {
            line_t& item = m_lines[index];
            size_t pos = item.first;
            size_t npt = item.second;
            float* data = m_xys.data()+pos;
            for(size_t 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 << "toolx::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 << "toolx::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 << "toolx::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 << "toolx::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 << "toolx::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.value(),m_scale)) return;
    FT_Pos face_height = m_face->size->metrics.height; //FT_Pos (long)

    m_tobj = gluNewTess();

    // NOTE : the gluTessCallback_<enum>() functions are tools/glutess specific.
    //        They had been introduced to avoid g++-8.1.0 warnings :
    //          warnings : cast between incompatible function types.
    ::gluTessCallback_GLU_TESS_BEGIN_DATA   (m_tobj,begin_cbk);
    ::gluTessCallback_GLU_TESS_END_DATA     (m_tobj,end_cbk);
    ::gluTessCallback_GLU_TESS_VERTEX_DATA  (m_tobj,vertex_cbk);
    ::gluTessCallback_GLU_TESS_COMBINE_DATA (m_tobj,combine_cbk);
    ::gluTessCallback_GLU_TESS_ERROR_DATA   (m_tobj,error_cbk);

    ::gluTessProperty(m_tobj,(GLUenum)GLU_TESS_WINDING_RULE,GLU_TESS_WINDING_ODD);

    if(strings.values().size()) {
      float yline = 0;
      tools_vforcit(std::string,strings.values(),vit) {
        const std::string& line = *vit;
        m_trans_x = 0;
        m_trans_y = yline;
        size_t ibeg = m_triangles.size(); //for hjust.
        for(auto it = line.cbegin(); it != line.cend(); ++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) {
          size_t num = m_triangles.size();
          for(size_t index=ibeg;index<num;index++) {
            std::pair<GLUenum,triangle_t>& item = m_triangles[index];
            size_t pos = item.second.first;
            size_t npt = item.second.second;
            float* data = m_xys.data()+pos;
            for(size_t i=0;i<npt;i++,data+=2) *data -= sx*0.5f;
          }
        } else if(hjust==tools::sg::right) {
          size_t num = m_triangles.size();
          for(size_t index=ibeg;index<num;index++) {
            std::pair<GLUenum,triangle_t>& item = m_triangles[index];
            size_t pos = item.second.first;
            size_t npt = item.second.second;
            float* data = m_xys.data()+pos;
            for(size_t 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;
        m_trans_x = 0;
        m_trans_y = yline;
        size_t 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) {
          size_t num = m_triangles.size();
          for(size_t index=ibeg;index<num;index++) {
            std::pair<GLUenum,triangle_t>& item = m_triangles[index];
            size_t pos = item.second.first;
            size_t npt = item.second.second;
            float* data = m_xys.data()+pos;
            for(size_t i=0;i<npt;i++,data+=2) *data -= sx*0.5f;
          }
        } else if(hjust==tools::sg::right) {
          size_t num = m_triangles.size();
          for(size_t index=ibeg;index<num;index++) {
            std::pair<GLUenum,triangle_t>& item = m_triangles[index];
            size_t pos = item.second.first;
            size_t npt = item.second.second;
            float* data = m_xys.data()+pos;
            for(size_t 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) {
    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 << "toolx::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 << "toolx::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 << "toolx::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 << "toolx::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 << "toolx::sg::text_freetype::char_triangles_2_gl :"
            << " for character " << a_unichar << ", winding anomaly."
            << std::endl;
    }}

    return true;
  }

  ////////////////////////////////////////////////////////
  /// bitmap /////////////////////////////////////////////
  ////////////////////////////////////////////////////////
  bool bitmap_2_gl(std::ostream& a_out) {
    FT_F26Dot6 wchar = (FT_F26Dot6)(m_char_height*64);
    FT_F26Dot6 hchar = (FT_F26Dot6)(m_char_height*64);
    FT_UInt hres = 72;
    FT_UInt vres = 72;
    FT_Error error = ::FT_Set_Char_Size(m_face,wchar,hchar,hres,vres);
    if(error) {
      a_out << "toolx::sg::text_freetype::bitmap_2_gl :"
            << " FT_Set_Char_Size : error : " << serror(error) << "."
            << std::endl;
      ::FT_Done_Face(m_face);
      m_face = 0;
      return false;
    }
    m_scale = height.value()/float(hchar);

    FT_Pos face_height = m_face->size->metrics.height; //FT_Pos (long)
    if(strings.values().size()) {
      float yline = 0;
      tools_vforcit(std::string,strings.values(),vit) {
        const std::string& line = *vit;
        m_trans_x = 0;
        m_trans_y = yline;
        size_t ibeg = m_tqs.size(); //for hjust.
        for(auto it = line.cbegin(); it != line.cend(); ++it) {
          if(!char_2_bitmap(a_out,*it + m_encoding_offset)) return false;
        }
        yline += -float(face_height)*m_scale; //height>0

       {float sx = m_trans_x;
        if(hjust==tools::sg::center) {
          size_t num = m_tqs.size();
          for(size_t index=ibeg;index<num;index++) {
            std::vector<tools::vec3f>& vcs = m_tqs[index]->corners.values();
            for(size_t i=0;i<4;i++) vcs[i][0] -= sx*0.5f;
          }
        } else if(hjust==tools::sg::right) {
          size_t num = m_tqs.size();
          for(size_t index=ibeg;index<num;index++) {
            std::vector<tools::vec3f>& vcs = m_tqs[index]->corners.values();
            for(size_t i=0;i<4;i++) vcs[i][0] -= sx;
          }
        }}
      }
    } else if(unitext.values().size()) {
      float yline = 0;
      tools_vforcit(uniline,unitext.values(),vit) {
        const uniline& line = *vit;
        m_trans_x = 0;
        m_trans_y = yline;
        size_t ibeg = m_tqs.size(); //for hjust.
        tools_vforcit(unichar,line,it) {
          if(!char_2_bitmap(a_out,*it)) return false;
        }
        yline += -float(face_height)*m_scale; //height>0

       {float sx = m_trans_x;
        if(hjust==tools::sg::center) {
          size_t num = m_tqs.size();
          for(size_t index=ibeg;index<num;index++) {
            std::vector<tools::vec3f>& vcs = m_tqs[index]->corners.values();
            for(size_t i=0;i<4;i++) vcs[i][0] -= sx*0.5f;
          }
        } else if(hjust==tools::sg::right) {
          size_t num = m_tqs.size();
          for(size_t index=ibeg;index<num;index++) {
            std::vector<tools::vec3f>& vcs = m_tqs[index]->corners.values();
            for(size_t i=0;i<4;i++) vcs[i][0] -= sx;
          }
        }}
      }
    }
    return true;
  }

  bool char_2_bitmap(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 << "toolx::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 << "toolx::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 << "toolx::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 << "toolx::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;
    }}

    FT_BitmapGlyph bitmap = (FT_BitmapGlyph)glyph;

    if( (bitmap->bitmap.pixel_mode!=ft_pixel_mode_mono) &&
        (bitmap->bitmap.pixel_mode!=ft_pixel_mode_grays) ){
      a_out << "toolx::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 << "toolx::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;
    }

    unsigned int img_w = 0;
    unsigned int img_h = 0;
    unsigned int img_bpp = 4;
    size_t img_sz = 0;

    if(bitmap->bitmap.pixel_mode==ft_pixel_mode_mono) {
      a_out << "toolx::sg::text_freetype::char_2_bitmap : mode_mono : not yet handled." << std::endl;

      img_h = bitmap->bitmap.rows;
      //WARNING : bitmap->bitmap.pitch != int((bitmap->bitmap.width+7)/8) !!!
      // OpenGL wants the below for cols.
      img_w = (bitmap->bitmap.width+7)/8;
      img_sz = img_w * img_h;

      ::FT_Done_Glyph(glyph);
      ::FT_Done_Face(m_face);
      m_face = 0;
      return false;

    } else { //ft_pixel_mode_grays
      if(int(bitmap->bitmap.width)!=bitmap->bitmap.pitch) {
        a_out << "toolx::sg::text_freetype::char_2_bitmap :"
              << " for font " << tools::sout(font.value())
              << " and for character " << a_unichar
              << "bitmap pitch (" << bitmap->bitmap.pitch << ") != width (" << bitmap->bitmap.width << ")."
              << std::endl;
        ::FT_Done_Glyph(glyph);
        ::FT_Done_Face(m_face);
        m_face = 0;
        return false;
      }
      img_w = bitmap->bitmap.width;
      img_h = bitmap->bitmap.rows;
      img_sz = img_w * img_h * img_bpp;
    }

    if(img_sz<=0) {
      // This may happen (for example for the space character).
    } else {
      tools::byte* img_buffer = new tools::byte[img_sz];
      if(!img_buffer) {
        a_out << "toolx::sg::text_freetype::char_2_bitmap :"
              << " for font " << tools::sout(font.value())
              << " and for character " << a_unichar
              << ", can't alloc bitmap buffer for character."
              << std::endl;
        ::FT_Done_Glyph(glyph);
        ::FT_Done_Face(m_face);
        m_face = 0;
        return false;
      }
      // The bitmap is upside down for OpenGL.
      float a,b;
      tools::colorf back_color = tools::colorf_white();
      back_color.set_a(0); //transparent background.
      typedef unsigned char uchar;
      for(unsigned int row=0;row<img_h;++row) {
        unsigned char* from = (unsigned char*)bitmap->bitmap.buffer + (bitmap->bitmap.rows-row-1)*bitmap->bitmap.pitch;
        unsigned char* to = img_buffer + row * img_w * img_bpp;
        for(unsigned int col=0;col<img_w;++col,++from) {
          a = float(*from)/255.0f;
          b = float(255-*from)/255.0f;
          *to = uchar(m_front_color.ruchar()*a+back_color.ruchar()*b);to++;
          *to = uchar(m_front_color.guchar()*a+back_color.guchar()*b);to++;
          *to = uchar(m_front_color.buchar()*a+back_color.buchar()*b);to++;
          *to = uchar(m_front_color.auchar()*a+back_color.auchar()*b);to++;
        }
      }

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

      std::vector<tools::vec3f> vcs;
      float scale = height.value()/m_char_height;
      float x_min = float(_bbox.xMin)*scale+m_trans_x;
      float x_max = float(_bbox.xMax)*scale+m_trans_x;
      float y_min = float(_bbox.yMin)*scale+m_trans_y;
      float y_max = float(_bbox.yMax)*scale+m_trans_y;

      vcs.push_back(tools::vec3f(x_min,y_min,0));
      vcs.push_back(tools::vec3f(x_max,y_min,0));
      vcs.push_back(tools::vec3f(x_max,y_max,0));
      vcs.push_back(tools::vec3f(x_min,y_max,0));

      tools::sg::tex_quadrilateral* _node = new tools::sg::tex_quadrilateral;
      _node->img.value().set(img_w,img_h,img_bpp,img_buffer,true);
      _node->expand = true;
      _node->back_color = tools::colorf_white(); //used as back pixel when expanding.
      _node->back_color.value().set_a(0);        //transparent background.
      //_node->nearest = false;                    //to have antialiasing on texture.
      _node->corners.set_values(vcs);

      m_bitmaps.add(_node);
      m_tqs.push_back(_node);
    }

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

    ::FT_Done_Glyph(glyph);

    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();
  }

  static void GLUAPIENTRY vertex_cbk(void* a_vertex,void* a_this) {
    text_freetype& self = *((text_freetype*)a_this);
    double* vertex = (double*)a_vertex;
    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*) {
  }

  ////////////////////////////////////////////////////////
  /// 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);

    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);
    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 << "toolx::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.

    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;

    size_t num = self.m_steps - 1;
    for(size_t 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;
    }

    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.

    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.

    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;

    size_t num = self.m_steps - 1;
    for(size_t 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
  class serrors : public std::map<int,std::string> {
    typedef std::map<int,std::string> parent;
  public:
    serrors(){
#undef __FTERRORS_H__
#define FT_ERROR_START_LIST
#define FT_ERROR_END_LIST
#define FT_ERRORDEF( e, v, s )  parent::operator[](e) = s;
#include FT_ERRORS_H
    }
    virtual ~serrors() {}
  protected:
    serrors(const serrors& a_from):parent(a_from) {}
    serrors& operator=(const serrors&){return *this;}
  };
  static std::string serror(int a_FT_Error) {
    static const serrors errs;
    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 << "toolx::sg::text_freetype::load_face :"
            << " freetype library not initialized."
            << std::endl;
      return;
    }

    if(m_verbose) {
      a_out << "toolx::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 << "toolx::sg::text_freetype::load_face :"
            << " no font given."
            << std::endl;
      return;
    }

    // look for embedded fonts:
   {unsigned int size;
    const unsigned char* buffer;
    if(parent::find_embedded_font(font.value(),size,buffer)) {
      FT_Error error =
        ::FT_New_Memory_Face(m_library,(const FT_Byte*)buffer,(FT_Long)size,0,&m_face);
      if(error) {
        a_out << "toolx::sg::text_freetype::load_face :"
              << " FT_New_Memory_Face : error : " << serror(error) << "."
              << std::endl;
        m_face = 0;
        return;
      }
      if(m_verbose) a_out << "toolx::sg::text_freetype::load_face : load embedded font ok." << std::endl;
      return;
    }}

    std::string file;

    if(parent::find_font_with_finders(font.value(),file)) {
    } else {
      tools::file::find_with_env(a_out,s_TOOLS_FONT_PATH(),font.value(),file,false);
    }
    
    if(file.empty()) {
      a_out << "toolx::sg::text_freetype::load_face :"
            << " font file not found for font "
            << tools::sout(font.value()) << "."
            << std::endl;
      return;
    }

    if(m_verbose) {
      a_out << "toolx::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 << "toolx::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 << "toolx::sg::text_freetype::load_face :"
            << " load ok."
            << std::endl;
    }

  }

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

  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_Error error = ::FT_Set_Char_Size(a_face,wchar,hchar,hres,vres);
    if(error) {
      a_out << "toolx::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;

    for(auto it = a_string.cbegin(); it != a_string.cend(); ++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) {
        a_sout.clear();
        ::FT_Done_Face(a_face);
        a_face = 0;
        return false;
      }

     {FT_Error error = ::FT_Load_Glyph(a_face,glyph_index,load_flags());
      if(error) {
        a_sout.clear();
        ::FT_Done_Face(a_face);
        a_face = 0;
        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;
      for(auto it = line.cbegin(); it != line.cend(); ++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) {
          ::FT_Done_Face(a_face);
          a_face = 0;
          return false;
        }

       {FT_Error error = ::FT_Load_Glyph(a_face,glyph_index,load_flags());
        if(error) {
          ::FT_Done_Face(a_face);
          a_face = 0;
          return false;
        }}

        float advance = float(a_face->glyph->advance.x)*scale;
        width += advance;
      }

      xmx = tools::max_of<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) {
          ::FT_Done_Face(a_face);
          a_face = 0;
          return false;
        }

       {FT_Error error = ::FT_Load_Glyph(a_face,glyph_index,load_flags());
        if(error) {
          ::FT_Done_Face(a_face);
          a_face = 0;
          return false;
        }}

        float advance = float(a_face->glyph->advance.x)*scale;
        width += advance;
      }

      xmx = tools::max_of<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;

    // 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;

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

      } else {
      }
    }

    ::FT_Done_Face(a_face);
    a_face = 0;
    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 tools/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;
  size_t m_steps;
  float m_delta;
  float m_delta2;
  float m_delta3;

  std::vector<float> m_xys;
  ////////////////////////////////////////////////////////
  /// outline lines //////////////////////////////////////
  ////////////////////////////////////////////////////////
  typedef std::pair<size_t,size_t> 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;
  size_t m_glutess_trids_num;
  std::vector<double*> m_combine_trids;
  size_t m_combine_trids_num;
  GLUenum m_mode;
  typedef std::pair<size_t,size_t> 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;
  size_t m_gsto_lines_sz;
  size_t m_gsto_sz;
  wndg_type m_wndg;
  tools::colorf m_front_color;
  tools::sg::group m_bitmaps;
  std::vector<tools::sg::tex_quadrilateral*> m_tqs;
  float m_char_height;
};

}}


#endif
