#ifndef tools_gl2ps
#define tools_gl2ps

// This version of gl2ps-1.4.2 contains four main changes:
// - it is pure header.
// - the code had been "namespace protected" by changing :
//     gl2ps_<xxx> to tools_gl2ps_<xxx>
//   and :
//     GL2PS_<xxx> to TOOLS_GL2PS_<xxx>
// - the code had been made thread safe by avoiding the internal writeable
//   static singleton gl2ps context object. With this version, you have to
//   create yourself a context, and pass it as first argument to all public
//   tools_gl2ps functions that you want to use with some code as:
//     ....
//     #include <tools/gl2ps>
//     ....
//     tools_GL2PScontext* gl2ps_context = tools_gl2psCreateContext();
//     ....
//     tools_gl2psBeginPage(gl2ps_context,...);
//     ...
//     tools_gl2psEndPage(gl2ps_context);
//     ....
//     tools_gl2psDeleteContext(gl2ps_context);
//     ....
// - it does not call directly OpenGL functions 
//     glIsEnabled,glBegin,glEnd,glGetFloatv,glVertex3f,glGetBooleanv,
//     glGetIntegerv,glRenderMode,glFeedbackBuffer,glPassThrough
//   but pointer to functions with the same signature. This permits to
//   use tools_gl2ps in a non OpenGL context, for example on primitives declared
//   by using tools_gl2psAddPolyPrimitive. If you want to use tools_gl2ps on primitives
//   got by using the OpenGL FEEDBACK mode (the "original way"), you have first
//   to declare the OpenGL upper functions on a tools_GL2PScontext with:
//     ....
//     #include <tools/gl2ps>
//     ....
//     #include <GL/gl.h>
//     ....
//     tools_GL2PScontext* gl2ps_context = tools_gl2psCreateContext();
//     ...
//     tools_gl2ps_gl_funcs_t _funcs = {
//       glIsEnabled,
//       glBegin,
//       glEnd,
//       glGetFloatv,
//       glVertex3f,
//       glGetBooleanv,
//       glGetIntegerv,
//       glRenderMode,
//       glFeedbackBuffer,
//       glPassThrough
//     };
//     tools_gl2ps_set_gl_funcs(gl2ps_context,&_funcs);
//     ...
//      
//    Guy Barrand. 15/March/2022
//

/*
 * GL2PS, an OpenGL to PostScript Printing Library
 * Copyright (C) 1999-2020 C. Geuzaine
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of either:
 *
 * a) the GNU Library General Public License as published by the Free
 * Software Foundation, either version 2 of the License, or (at your
 * option) any later version; or
 *
 * b) the GL2PS License as published by Christophe Geuzaine, either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See either
 * the GNU Library General Public License or the GL2PS License for
 * more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library in the file named "COPYING.LGPL";
 * if not, write to the Free Software Foundation, Inc., 51 Franklin
 * Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * You should have received a copy of the GL2PS License with this
 * library in the file named "COPYING.GL2PS"; if not, I will be glad
 * to provide one.
 *
 * For the latest info about gl2ps and a full list of contributors,
 * see http://www.geuz.org/gl2ps/.
 *
 * Please report all bugs and problems to <gl2ps@geuz.org>.
 */

#include "gl2ps_def.h"

#include <stdlib.h>
#include <stdio.h>

#include <math.h>
#include <string.h>
#include <sys/types.h>
#include <stdarg.h>
#include <time.h>
#include <float.h>

#if defined(TOOLS_GL2PS_HAVE_ZLIB)
#include <zlib.h>
#endif

#if defined(TOOLS_GL2PS_HAVE_LIBPNG)
#include <png.h>
#endif

/*********************************************************************
 *
 * Private definitions, data structures and prototypes
 *
 *********************************************************************/

/* Magic numbers (assuming that the order of magnitude of window
   coordinates is 10^3) */

#define TOOLS_GL2PS_EPSILON       5.0e-3F
#define TOOLS_GL2PS_ZSCALE        1000.0F
#define TOOLS_GL2PS_ZOFFSET       5.0e-2F
#define TOOLS_GL2PS_ZOFFSET_LARGE 20.0F
#define TOOLS_GL2PS_ZERO(arg)     (fabs(arg) < 1.e-20)

/* BSP tree primitive comparison */

#define TOOLS_GL2PS_COINCIDENT  1
#define TOOLS_GL2PS_IN_FRONT_OF 2
#define TOOLS_GL2PS_IN_BACK_OF  3
#define TOOLS_GL2PS_SPANNING    4

/* 2D BSP tree primitive comparison */

#define TOOLS_GL2PS_POINT_COINCIDENT 0
#define TOOLS_GL2PS_POINT_INFRONT    1
#define TOOLS_GL2PS_POINT_BACK       2

/* Internal feedback buffer pass-through tokens */

#define TOOLS_GL2PS_BEGIN_OFFSET_TOKEN   1
#define TOOLS_GL2PS_END_OFFSET_TOKEN     2
#define TOOLS_GL2PS_BEGIN_BOUNDARY_TOKEN 3
#define TOOLS_GL2PS_END_BOUNDARY_TOKEN   4
#define TOOLS_GL2PS_BEGIN_STIPPLE_TOKEN  5
#define TOOLS_GL2PS_END_STIPPLE_TOKEN    6
#define TOOLS_GL2PS_POINT_SIZE_TOKEN     7
#define TOOLS_GL2PS_LINE_CAP_TOKEN       8
#define TOOLS_GL2PS_LINE_JOIN_TOKEN      9
#define TOOLS_GL2PS_LINE_WIDTH_TOKEN     10
#define TOOLS_GL2PS_BEGIN_BLEND_TOKEN    11
#define TOOLS_GL2PS_END_BLEND_TOKEN      12
#define TOOLS_GL2PS_SRC_BLEND_TOKEN      13
#define TOOLS_GL2PS_DST_BLEND_TOKEN      14
#define TOOLS_GL2PS_IMAGEMAP_TOKEN       15
#define TOOLS_GL2PS_DRAW_PIXELS_TOKEN    16
#define TOOLS_GL2PS_TEXT_TOKEN           17

typedef enum {
  T_UNDEFINED    = -1,
  T_CONST_COLOR  = 1,
  T_VAR_COLOR    = 1<<1,
  T_ALPHA_1      = 1<<2,
  T_ALPHA_LESS_1 = 1<<3,
  T_VAR_ALPHA    = 1<<4
} TOOLS_GL2PS_TRIANGLE_PROPERTY;

typedef tools_GLfloat tools_GL2PSplane[4];

typedef struct tools_GL2PSbsptree2d_ tools_GL2PSbsptree2d;

struct tools_GL2PSbsptree2d_ {
  tools_GL2PSplane plane;
  tools_GL2PSbsptree2d *front, *back;
};

typedef struct {
  tools_GLint nmax, size, incr, n;
  char *array;
} tools_GL2PSlist;

typedef struct tools_GL2PSbsptree_ tools_GL2PSbsptree;

struct tools_GL2PSbsptree_ {
  tools_GL2PSplane plane;
  tools_GL2PSlist *primitives;
  tools_GL2PSbsptree *front, *back;
};

typedef struct {
  tools_GL2PSvertex vertex[3];
  int prop;
} tools_GL2PStriangle;

typedef struct {
  tools_GLshort fontsize;
  char *str, *fontname;
  /* Note: for a 'special' string, 'alignment' holds the format
     (PostScript, PDF, etc.) of the special string */
  tools_GLint alignment;
  tools_GLfloat angle;
} tools_GL2PSstring;

typedef struct {
  tools_GLsizei width, height;
  /* Note: for an imagemap, 'type' indicates if it has already been
     written to the file or not, and 'format' indicates if it is
     visible or not */
  tools_GLenum format, type;
  tools_GLfloat zoom_x, zoom_y;
  tools_GLfloat *pixels;
} tools_GL2PSimage;

typedef struct tools_GL2PSimagemap_ tools_GL2PSimagemap;

struct tools_GL2PSimagemap_ {
  tools_GL2PSimage *image;
  tools_GL2PSimagemap *next;
};

typedef struct {
  tools_GLshort type, numverts;
  tools_GLushort pattern;
  char boundary, offset, culled;
  tools_GLint factor, linecap, linejoin, sortid;
  tools_GLfloat width, ofactor, ounits;
  tools_GL2PSvertex *verts;
  union {
    tools_GL2PSstring *text;
    tools_GL2PSimage *image;
  } data;
} tools_GL2PSprimitive;

typedef struct {
#if defined(TOOLS_GL2PS_HAVE_ZLIB)
  Bytef *dest, *src, *start;
  uLongf destLen, srcLen;
#else
  int dummy;
#endif
} tools_GL2PScompress;

typedef struct{
  tools_GL2PSlist* ptrlist;
  int gsno, fontno, imno, shno, maskshno, trgroupno;
  int gsobjno, fontobjno, imobjno, shobjno, maskshobjno, trgroupobjno;
} tools_GL2PSpdfgroup;

typedef struct tools_GL2PScontextRec {
  /* General */
  tools_GLint format, sort, options, colorsize, colormode, buffersize;
  tools_GLint lastlinecap, lastlinejoin;
  char *title, *producer, *filename;
  tools_GLboolean boundary, blending;
  tools_GLfloat *feedback, lastlinewidth;
  tools_GLint viewport[4], blendfunc[2], lastfactor;
  tools_GL2PSrgba *colormap, lastrgba, threshold, bgcolor;
  tools_GLushort lastpattern;
  tools_GL2PSvertex lastvertex;
  tools_GL2PSlist *primitives, *auxprimitives;
  FILE *stream;
  tools_GL2PScompress *compress;
  tools_GLboolean header;
  tools_GL2PSvertex rasterpos;
  tools_GLboolean forcerasterpos;

  /* BSP-specific */
  tools_GLint maxbestroot;

  /* Occlusion culling-specific */
  tools_GLboolean zerosurfacearea;
  tools_GL2PSbsptree2d *imagetree;
  tools_GL2PSprimitive *primitivetoadd;

  /* PDF-specific */
  int streamlength;
  tools_GL2PSlist *pdfprimlist, *pdfgrouplist;
  int *xreflist;
  int objects_stack; /* available objects */
  int extgs_stack; /* graphics state object number */
  int font_stack; /* font object number */
  int im_stack; /* image object number */
  int trgroupobjects_stack; /* xobject numbers */
  int shader_stack; /* shader object numbers */
  int mshader_stack; /* mask shader object numbers */

  /* for image map list */
  tools_GL2PSimagemap *imagemap_head;
  tools_GL2PSimagemap *imagemap_tail;

  /* for TEX scaling */
  tools_GLfloat tex_scaling;

  /*G.Barrand : OpenGL functions:*/
  tools_gl2ps_gl_funcs_t m_gl_funcs;
} tools_GL2PScontext;

typedef struct {
  void  (*printHeader)(tools_GL2PScontext*);
  void  (*printFooter)(tools_GL2PScontext*);
  void  (*beginViewport)(tools_GL2PScontext*,tools_GLint viewport[4]);
  tools_GLint (*endViewport)(tools_GL2PScontext*);
  void  (*printPrimitive)(tools_GL2PScontext*,void *data);
  void  (*printFinalPrimitive)(tools_GL2PScontext*);
  const char *file_extension;
  const char *description;
} tools_GL2PSbackend;

/* The gl2ps context. gl2ps is not thread safe (we should create a
   local GL2PScontext during tools_gl2psBeginPage) */

//static GL2PScontext *gl2ps = NULL;

/* Need to forward-declare this one */

inline tools_GLint tools_gl2psPrintPrimitives(tools_GL2PScontext*);

/*********************************************************************
 *
 * Utility routines
 *
 *********************************************************************/

inline void tools_gl2psMsg(tools_GLint level, const char *fmt, ...)
{
  va_list args;

/*if(!(gl2ps->options & TOOLS_GL2PS_SILENT))*/{
    switch(level){
    case TOOLS_GL2PS_INFO : fprintf(stderr, "GL2PS info: "); break;
    case TOOLS_GL2PS_WARNING : fprintf(stderr, "GL2PS warning: "); break;
    case TOOLS_GL2PS_ERROR : fprintf(stderr, "GL2PS error: "); break;
    }
    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);
    fprintf(stderr, "\n");
  }
  /* if(level == TOOLS_GL2PS_ERROR) exit(1); */
}

inline void *tools_gl2psMalloc(size_t size)
{
  void *ptr;

  if(!size) return NULL;
  ptr = malloc(size);
  if(!ptr){
    tools_gl2psMsg(TOOLS_GL2PS_ERROR, "Couldn't allocate requested memory");
    return NULL;
  }
  return ptr;
}

inline void *tools_gl2psRealloc(void *ptr, size_t size)
{
  void *orig = ptr;
  if(!size) return NULL;
  ptr = realloc(orig, size);
  if(!ptr){
    tools_gl2psMsg(TOOLS_GL2PS_ERROR, "Couldn't reallocate requested memory");
    free(orig);
    return NULL;
  }
  return ptr;
}

inline void tools_gl2psFree(void *ptr)
{
  if(!ptr) return;
  free(ptr);
}

/*G.Barrand: begin*/
inline tools_GLboolean tools_dummy_glIsEnabled      (tools_GLenum) {return TOOLS_GL_FALSE;}
inline void      tools_dummy_glBegin          (tools_GLenum) {
  /*printf("debug : tools_dummy_glBegin.\n");*/
}
inline void      tools_dummy_glEnd            () {}
inline void      tools_dummy_glGetFloatv      (tools_GLenum,tools_GLfloat*) {}
inline void      tools_dummy_glVertex3f       (tools_GLfloat,tools_GLfloat,tools_GLfloat) {}
inline void      tools_dummy_glGetBooleanv    (tools_GLenum,tools_GLboolean*) {}
inline void      tools_dummy_glGetIntegerv    (tools_GLenum,tools_GLint*) {}
inline tools_GLint     tools_dummy_glRenderMode     (tools_GLenum) {
  /*printf("debug : tools_dummy_glRenderMode.\n");*/
  return 0;
}
inline void      tools_dummy_glFeedbackBuffer (tools_GLsizei,tools_GLenum,tools_GLfloat*) {}
inline void      tools_dummy_glPassThrough    (tools_GLfloat ) {}

inline tools_GL2PScontext* tools_gl2psCreateContext(void) {
  tools_GL2PScontext* gl2ps = (tools_GL2PScontext*)tools_gl2psMalloc(sizeof(tools_GL2PScontext));
  if(!gl2ps) return 0;

  gl2ps->format = 0;
  gl2ps->sort = 0;
  gl2ps->options = 0;
  gl2ps->colorsize = 0;
  gl2ps->colormode = 0;
  gl2ps->buffersize = 0;
  gl2ps->lastlinecap = 0;
  gl2ps->lastlinejoin = 0;
  gl2ps->title = NULL;
  gl2ps->producer = NULL;
  gl2ps->filename = NULL;
  gl2ps->boundary = TOOLS_GL_FALSE;
  gl2ps->blending = TOOLS_GL_FALSE;
  gl2ps->feedback = NULL;
  gl2ps->lastlinewidth = 0;
  gl2ps->viewport[0] = 0;
  gl2ps->viewport[1] = 0;
  gl2ps->viewport[2] = 0;
  gl2ps->viewport[3] = 0;

  gl2ps->blendfunc[0] = 0;
  gl2ps->blendfunc[1] = 0;

  gl2ps->lastfactor = 0;
  gl2ps->colormap = NULL;
  gl2ps->lastrgba[0] = 0;
  gl2ps->lastrgba[1] = 0;
  gl2ps->lastrgba[2] = 0;
  gl2ps->lastrgba[3] = 0;
  gl2ps->threshold[0] = 0;
  gl2ps->threshold[1] = 0;
  gl2ps->threshold[2] = 0;
  gl2ps->threshold[3] = 0;
  gl2ps->bgcolor[0] = 0;
  gl2ps->bgcolor[1] = 0;
  gl2ps->bgcolor[2] = 0;
  gl2ps->bgcolor[3] = 0;
  
  gl2ps->lastpattern = 0;
  gl2ps->lastvertex.xyz[0] = 0;
  gl2ps->lastvertex.xyz[1] = 0;
  gl2ps->lastvertex.xyz[2] = 0;
  gl2ps->lastvertex.rgba[0] = 0;
  gl2ps->lastvertex.rgba[1] = 0;
  gl2ps->lastvertex.rgba[2] = 0;
  gl2ps->lastvertex.rgba[3] = 0;
  
  gl2ps->primitives = NULL;
  gl2ps->auxprimitives = NULL;
  gl2ps->stream = NULL;
  gl2ps->compress = NULL;
  gl2ps->header = TOOLS_GL_FALSE;
  gl2ps->rasterpos.xyz[0] = 0;
  gl2ps->rasterpos.xyz[1] = 0;
  gl2ps->rasterpos.xyz[2] = 0;
  gl2ps->rasterpos.rgba[0] = 0;
  gl2ps->rasterpos.rgba[1] = 0;
  gl2ps->rasterpos.rgba[2] = 0;
  gl2ps->rasterpos.rgba[3] = 0;
  
  gl2ps->forcerasterpos = TOOLS_GL_FALSE;
  gl2ps->maxbestroot = 0;
  gl2ps->zerosurfacearea = TOOLS_GL_FALSE;
  gl2ps->imagetree = NULL;
  gl2ps->primitivetoadd = NULL;

  gl2ps->streamlength = 0;
  gl2ps->pdfprimlist = NULL;
  gl2ps->pdfgrouplist = NULL;
  gl2ps->xreflist = NULL;
  
  gl2ps->objects_stack = 0;
  gl2ps->extgs_stack = 0;
  gl2ps->font_stack = 0;
  gl2ps->im_stack = 0;
  gl2ps->trgroupobjects_stack = 0;
  gl2ps->shader_stack = 0;
  gl2ps->mshader_stack = 0;
  gl2ps->imagemap_head = NULL;
  gl2ps->imagemap_tail = NULL;

  gl2ps->m_gl_funcs.m_glIsEnabled = tools_dummy_glIsEnabled;
  gl2ps->m_gl_funcs.m_glBegin = tools_dummy_glBegin;
  gl2ps->m_gl_funcs.m_glEnd = tools_dummy_glEnd;
  gl2ps->m_gl_funcs.m_glGetFloatv = tools_dummy_glGetFloatv;
  gl2ps->m_gl_funcs.m_glVertex3f = tools_dummy_glVertex3f;
  gl2ps->m_gl_funcs.m_glGetBooleanv = tools_dummy_glGetBooleanv;
  gl2ps->m_gl_funcs.m_glGetIntegerv = tools_dummy_glGetIntegerv;
  gl2ps->m_gl_funcs.m_glRenderMode = tools_dummy_glRenderMode;
  gl2ps->m_gl_funcs.m_glFeedbackBuffer = tools_dummy_glFeedbackBuffer;
  gl2ps->m_gl_funcs.m_glPassThrough = tools_dummy_glPassThrough;

  return gl2ps;
}
inline void tools_gl2psDeleteContext(tools_GL2PScontext* a_context) {
  tools_gl2psFree(a_context);
}  

inline void tools_gl2ps_set_gl_funcs(tools_GL2PScontext* gl2ps, tools_gl2ps_gl_funcs_t* a_funcs) {
  gl2ps->m_gl_funcs.m_glIsEnabled = a_funcs->m_glIsEnabled;
  gl2ps->m_gl_funcs.m_glBegin = a_funcs->m_glBegin;
  gl2ps->m_gl_funcs.m_glEnd = a_funcs->m_glEnd;
  gl2ps->m_gl_funcs.m_glGetFloatv = a_funcs->m_glGetFloatv;
  gl2ps->m_gl_funcs.m_glVertex3f = a_funcs->m_glVertex3f;
  gl2ps->m_gl_funcs.m_glGetBooleanv = a_funcs->m_glGetBooleanv;
  gl2ps->m_gl_funcs.m_glGetIntegerv = a_funcs->m_glGetIntegerv;
  gl2ps->m_gl_funcs.m_glRenderMode = a_funcs->m_glRenderMode;
  gl2ps->m_gl_funcs.m_glFeedbackBuffer = a_funcs->m_glFeedbackBuffer;
  gl2ps->m_gl_funcs.m_glPassThrough = a_funcs->m_glPassThrough;
}

inline void tools_gl2ps_reset_gl_funcs(tools_GL2PScontext* gl2ps) {
  gl2ps->m_gl_funcs.m_glIsEnabled = tools_dummy_glIsEnabled;
  gl2ps->m_gl_funcs.m_glBegin = tools_dummy_glBegin;
  gl2ps->m_gl_funcs.m_glEnd = tools_dummy_glEnd;
  gl2ps->m_gl_funcs.m_glGetFloatv = tools_dummy_glGetFloatv;
  gl2ps->m_gl_funcs.m_glVertex3f = tools_dummy_glVertex3f;
  gl2ps->m_gl_funcs.m_glGetBooleanv = tools_dummy_glGetBooleanv;
  gl2ps->m_gl_funcs.m_glGetIntegerv = tools_dummy_glGetIntegerv;
  gl2ps->m_gl_funcs.m_glRenderMode = tools_dummy_glRenderMode;
  gl2ps->m_gl_funcs.m_glFeedbackBuffer = tools_dummy_glFeedbackBuffer;
  gl2ps->m_gl_funcs.m_glPassThrough = tools_dummy_glPassThrough;
}

#define tools_glIsEnabled      gl2ps->m_gl_funcs.m_glIsEnabled
#define tools_glBegin          gl2ps->m_gl_funcs.m_glBegin
#define tools_glEnd            gl2ps->m_gl_funcs.m_glEnd
#define tools_glGetFloatv      gl2ps->m_gl_funcs.m_glGetFloatv
#define tools_glVertex3f       gl2ps->m_gl_funcs.m_glVertex3f
#define tools_glGetBooleanv    gl2ps->m_gl_funcs.m_glGetBooleanv
#define tools_glGetIntegerv    gl2ps->m_gl_funcs.m_glGetIntegerv
#define tools_glRenderMode     gl2ps->m_gl_funcs.m_glRenderMode
#define tools_glFeedbackBuffer gl2ps->m_gl_funcs.m_glFeedbackBuffer
#define tools_glPassThrough    gl2ps->m_gl_funcs.m_glPassThrough

/*G.Barrand: end*/

inline int tools_gl2psWriteBigEndian(tools_GL2PScontext* gl2ps, unsigned long data, int bytes)
{
  int i;
  int size = sizeof(unsigned long);
  for(i = 1; i <= bytes; ++i){
    fputc(0xff & (data >> (size - i) * 8), gl2ps->stream);
  }
  return bytes;
}

/* zlib compression helper routines */

#if defined(TOOLS_GL2PS_HAVE_ZLIB)

inline void tools_gl2psSetupCompress(tools_GL2PScontext* gl2ps)
{
  gl2ps->compress = (tools_GL2PScompress*)tools_gl2psMalloc(sizeof(tools_GL2PScompress));
  gl2ps->compress->src = NULL;
  gl2ps->compress->start = NULL;
  gl2ps->compress->dest = NULL;
  gl2ps->compress->srcLen = 0;
  gl2ps->compress->destLen = 0;
}

inline void tools_gl2psFreeCompress(tools_GL2PScontext* gl2ps)
{
  if(!gl2ps->compress)
    return;
  tools_gl2psFree(gl2ps->compress->start);
  tools_gl2psFree(gl2ps->compress->dest);
  gl2ps->compress->src = NULL;
  gl2ps->compress->start = NULL;
  gl2ps->compress->dest = NULL;
  gl2ps->compress->srcLen = 0;
  gl2ps->compress->destLen = 0;
}

inline int tools_gl2psAllocCompress(tools_GL2PScontext* gl2ps, unsigned int srcsize)
{
  tools_gl2psFreeCompress(gl2ps);

  if(!gl2ps->compress || !srcsize)
    return TOOLS_GL2PS_ERROR;

  gl2ps->compress->srcLen = srcsize;
  gl2ps->compress->destLen = (int)ceil(1.001 * gl2ps->compress->srcLen + 12);
  gl2ps->compress->src = (Bytef*)tools_gl2psMalloc(gl2ps->compress->srcLen);
  gl2ps->compress->start = gl2ps->compress->src;
  gl2ps->compress->dest = (Bytef*)tools_gl2psMalloc(gl2ps->compress->destLen);

  return TOOLS_GL2PS_SUCCESS;
}

inline void *tools_gl2psReallocCompress(tools_GL2PScontext* gl2ps, unsigned int srcsize)
{
  if(!gl2ps->compress || !srcsize)
    return NULL;

  if(srcsize < gl2ps->compress->srcLen)
    return gl2ps->compress->start;

  gl2ps->compress->srcLen = srcsize;
  gl2ps->compress->destLen = (int)ceil(1.001 * gl2ps->compress->srcLen + 12);
  gl2ps->compress->src = (Bytef*)tools_gl2psRealloc(gl2ps->compress->src,
                                              gl2ps->compress->srcLen);
  gl2ps->compress->start = gl2ps->compress->src;
  gl2ps->compress->dest = (Bytef*)tools_gl2psRealloc(gl2ps->compress->dest,
                                               gl2ps->compress->destLen);

  return gl2ps->compress->start;
}

inline int tools_gl2psWriteBigEndianCompress(tools_GL2PScontext* gl2ps, unsigned long data, int bytes)
{
  int i;
  int size = sizeof(unsigned long);
  for(i = 1; i <= bytes; ++i){
    *gl2ps->compress->src = (Bytef)(0xff & (data >> (size-i) * 8));
    ++gl2ps->compress->src;
  }
  return bytes;
}

inline int tools_gl2psDeflate(tools_GL2PScontext* gl2ps)
{
  /* For compatibility with older zlib versions, we use compress(...)
     instead of compress2(..., Z_BEST_COMPRESSION) */
  return compress(gl2ps->compress->dest, &gl2ps->compress->destLen,
                  gl2ps->compress->start, gl2ps->compress->srcLen);
}

#endif

inline int tools_gl2psPrintf(tools_GL2PScontext* gl2ps, const char* fmt, ...)
{
  int ret;
  va_list args;

#if defined(TOOLS_GL2PS_HAVE_ZLIB)
//static char buf[1024]; //G.Barrand: avoid this writeable static.
//char *bufptr = buf;
//tools_GLboolean freebuf = TOOLS_GL_FALSE;
  char* bufptr = (char *)tools_gl2psMalloc(1024);
  tools_GLboolean freebuf = TOOLS_GL_TRUE;
  unsigned int oldsize = 0;
#if !defined(TOOLS_GL2PS_HAVE_NO_VSNPRINTF)
  /* Try writing the string to a 1024 byte buffer. If it is too small to fit,
     keep trying larger sizes until it does. */
//int bufsize = sizeof(buf);
  int bufsize = 1024;
#endif
  if(gl2ps->options & TOOLS_GL2PS_COMPRESS){
    va_start(args, fmt);
#if defined(TOOLS_GL2PS_HAVE_NO_VSNPRINTF)
    ret = vsprintf(buf, fmt, args);
#else
    ret = vsnprintf(bufptr, bufsize, fmt, args);
#endif
    va_end(args);
#if !defined(TOOLS_GL2PS_HAVE_NO_VSNPRINTF)
    while(ret >= (bufsize - 1) || ret < 0){
      /* Too big. Allocate a new buffer. */
      bufsize *= 2;
      if(freebuf == TOOLS_GL_TRUE) tools_gl2psFree(bufptr);
      bufptr = (char *)tools_gl2psMalloc(bufsize);
      freebuf = TOOLS_GL_TRUE;
      va_start(args, fmt);
      ret = vsnprintf(bufptr, bufsize, fmt, args);
      va_end(args);
    }
#endif
    oldsize = gl2ps->compress->srcLen;
    gl2ps->compress->start = (Bytef*)tools_gl2psReallocCompress(gl2ps, oldsize + ret);
    memcpy(gl2ps->compress->start + oldsize, bufptr, ret);
    if(freebuf == TOOLS_GL_TRUE) tools_gl2psFree(bufptr);
    ret = 0;
  }
  else{
#endif
    va_start(args, fmt);
    ret = vfprintf(gl2ps->stream, fmt, args);
    va_end(args);
#if defined(TOOLS_GL2PS_HAVE_ZLIB)
  }
#endif
  return ret;
}

inline void tools_gl2psPrintGzipHeader(tools_GL2PScontext* gl2ps)
{
#if defined(TOOLS_GL2PS_HAVE_ZLIB)
  char tmp[10] = {'\x1f', '\x8b', /* magic numbers: 0x1f, 0x8b */
                  8, /* compression method: Z_DEFLATED */
                  0, /* flags */
                  0, 0, 0, 0, /* time */
                  2, /* extra flags: max compression */
                  '\x03'}; /* OS code: 0x03 (Unix) */

  if(gl2ps->options & TOOLS_GL2PS_COMPRESS){
    tools_gl2psSetupCompress(gl2ps);
    /* add the gzip file header */
    fwrite(tmp, 10, 1, gl2ps->stream);
  }
#endif
  (void)gl2ps;
}

inline void tools_gl2psPrintGzipFooter(tools_GL2PScontext* gl2ps)
{
#if defined(TOOLS_GL2PS_HAVE_ZLIB)
  int n;
  uLong crc, len;
  char tmp[8];

  if(gl2ps->options & TOOLS_GL2PS_COMPRESS){
    if(Z_OK != tools_gl2psDeflate(gl2ps)){
      tools_gl2psMsg(TOOLS_GL2PS_ERROR, "Zlib deflate error");
    }
    else{
      /* determine the length of the header in the zlib stream */
      n = 2; /* CMF+FLG */
      if(gl2ps->compress->dest[1] & (1<<5)){
        n += 4; /* DICTID */
      }
      /* write the data, without the zlib header and footer */
      fwrite(gl2ps->compress->dest+n, gl2ps->compress->destLen-(n+4),
             1, gl2ps->stream);
      /* add the gzip file footer */
      crc = crc32(0L, gl2ps->compress->start, gl2ps->compress->srcLen);
      for(n = 0; n < 4; ++n){
        tmp[n] = (char)(crc & 0xff);
        crc >>= 8;
      }
      len = gl2ps->compress->srcLen;
      for(n = 4; n < 8; ++n){
        tmp[n] = (char)(len & 0xff);
        len >>= 8;
      }
      fwrite(tmp, 8, 1, gl2ps->stream);
    }
    tools_gl2psFreeCompress(gl2ps);
    tools_gl2psFree(gl2ps->compress);
    gl2ps->compress = NULL;
  }
#endif
  (void)gl2ps;
}

/* The list handling routines */

inline void tools_gl2psListRealloc(tools_GL2PSlist *list, tools_GLint n)
{
  if(!list){
    tools_gl2psMsg(TOOLS_GL2PS_ERROR, "Cannot reallocate NULL list");
    return;
  }
  if(n <= 0) return;
  if(!list->array){
    list->nmax = n;
    list->array = (char*)tools_gl2psMalloc(list->nmax * list->size);
  }
  else{
    if(n > list->nmax){
      list->nmax = ((n - 1) / list->incr + 1) * list->incr;
      list->array = (char*)tools_gl2psRealloc(list->array,
                                        list->nmax * list->size);
    }
  }
}

inline tools_GL2PSlist *tools_gl2psListCreate(tools_GLint n, tools_GLint incr, tools_GLint size)
{
  tools_GL2PSlist *list;

  if(n < 0) n = 0;
  if(incr <= 0) incr = 1;
  list = (tools_GL2PSlist*)tools_gl2psMalloc(sizeof(tools_GL2PSlist));
  list->nmax = 0;
  list->incr = incr;
  list->size = size;
  list->n = 0;
  list->array = NULL;
  tools_gl2psListRealloc(list, n);
  return list;
}

inline void tools_gl2psListReset(tools_GL2PSlist *list)
{
  if(!list) return;
  list->n = 0;
}

inline void tools_gl2psListDelete(tools_GL2PSlist *list)
{
  if(!list) return;
  tools_gl2psFree(list->array);
  tools_gl2psFree(list);
}

inline void tools_gl2psListAdd(tools_GL2PSlist *list, void *data)
{
  if(!list){
    tools_gl2psMsg(TOOLS_GL2PS_ERROR, "Cannot add into unallocated list");
    return;
  }
  list->n++;
  tools_gl2psListRealloc(list, list->n);
  memcpy(&list->array[(list->n - 1) * list->size], data, list->size);
}

inline int tools_gl2psListNbr(tools_GL2PSlist *list)
{
  if(!list)
    return 0;
  return list->n;
}

inline void *tools_gl2psListPointer(tools_GL2PSlist *list, tools_GLint idx)
{
  if(!list){
    tools_gl2psMsg(TOOLS_GL2PS_ERROR, "Cannot point into unallocated list");
    return NULL;
  }
  if((idx < 0) || (idx >= list->n)){
    tools_gl2psMsg(TOOLS_GL2PS_ERROR, "Wrong list index in tools_gl2psListPointer");
    return NULL;
  }
  return &list->array[idx * list->size];
}

//G.Barrand: begin:
inline bool tools_gl2psPortableSort(void* a_items,size_t a_nitem,size_t a_item_size,int(*a_cmp)(const void*,const void*)) {
  //  We observed that qsort on macOS/clang, Linux/gcc, Windows/VisualC++
  // does not produce the same output list for objects considered as "the same".
  // "Same objects" are put correctly contiguously but not in the same order
  // according the platform. In case of gl2ps, if using qsort we have, at end,
  // primitives in the output file which are not in the same order according the platform.
  // Then, we let the possibility to use a portable sort algorithm that will
  // give the same sorted list, and then the same output file, on all platforms.
  if(a_nitem<=1) return true;
  if(!a_item_size) return true;
  void* tmp = ::malloc(a_item_size);
  if(!tmp) return false;
  char* p = (char*)a_items;
  size_t i,j;
  char* a = p;char* b;
  for(i=0;i<a_nitem;i++,a+=a_item_size) {
    b = p+a_item_size*(i+1);
    for(j=i+1;j<a_nitem;j++,b+=a_item_size) {
      if(a_cmp(b,a)>=0) continue;  //b>=a
      ::memcpy(tmp,a,a_item_size);
      ::memcpy(a,b,a_item_size);
      ::memcpy(b,tmp,a_item_size);
    }
  }
  ::free(tmp);
  return true;
}
//G.Barrand: end.

inline void tools_gl2psListSort(tools_GL2PScontext* gl2ps,
                                tools_GL2PSlist *list,
                                int (*fcmp)(const void *a, const void *b))
{
  if(!list) return;
  if(gl2ps->options & TOOLS_GL2PS_PORTABLE_SORT) {
    tools_gl2psPortableSort(list->array, list->n, list->size, fcmp);
  } else {
    ::qsort(list->array, list->n, list->size, fcmp);
  }
}

/* Must be a list of tools_GL2PSprimitives. */
inline void tools_gl2psListAssignSortIds(tools_GL2PSlist *list)
{
  tools_GLint i;
  for(i = 0; i < tools_gl2psListNbr(list); i++){
    (*(tools_GL2PSprimitive**)tools_gl2psListPointer(list, i))->sortid = i;
  }
}

inline void tools_gl2psListAction(tools_GL2PSlist *list, void (*action)(void *data))
{
  tools_GLint i;

  for(i = 0; i < tools_gl2psListNbr(list); i++){
    (*action)(tools_gl2psListPointer(list, i));
  }
}

inline void tools_gl2psListActionInverse(tools_GL2PSlist *list, void (*action)(void *data))
{
  tools_GLint i;

  for(i = tools_gl2psListNbr(list); i > 0; i--){
    (*action)(tools_gl2psListPointer(list, i-1));
  }
}

inline void tools_gl2psListActionContext(tools_GL2PScontext* gl2ps, tools_GL2PSlist *list, void (*action)(tools_GL2PScontext*, void *data))
{
  tools_GLint i;

  for(i = 0; i < tools_gl2psListNbr(list); i++){
    (*action)(gl2ps, tools_gl2psListPointer(list, i));
  }
}

inline void tools_gl2psListActionInverseContext(tools_GL2PScontext* gl2ps, tools_GL2PSlist *list, void (*action)(tools_GL2PScontext*, void *data))
{
  tools_GLint i;

  for(i = tools_gl2psListNbr(list); i > 0; i--){
    (*action)(gl2ps, tools_gl2psListPointer(list, i-1));
  }
}

#if defined(TOOLS_GL2PS_HAVE_LIBPNG)

inline void tools_gl2psListRead(tools_GL2PSlist *list, int index, void *data)
{
  if((index < 0) || (index >= list->n))
    tools_gl2psMsg(TOOLS_GL2PS_ERROR, "Wrong list index in tools_gl2psListRead");
  memcpy(data, &list->array[index * list->size], list->size);
}

inline void tools_gl2psEncodeBase64Block(unsigned char in[3], unsigned char out[4], int len)
{
  static const char cb64[] =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

  out[0] = cb64[ in[0] >> 2 ];
  out[1] = cb64[ ((in[0] & 0x03) << 4) | ((in[1] & 0xf0) >> 4) ];
  out[2] = (len > 1) ? cb64[ ((in[1] & 0x0f) << 2) | ((in[2] & 0xc0) >> 6) ] : '=';
  out[3] = (len > 2) ? cb64[ in[2] & 0x3f ] : '=';
}

inline void tools_gl2psListEncodeBase64(tools_GL2PSlist *list)
{
  unsigned char *buffer, in[3], out[4];
  int i, n, index, len;

  n = list->n * list->size;
  buffer = (unsigned char*)tools_gl2psMalloc(n * sizeof(unsigned char));
  memcpy(buffer, list->array, n * sizeof(unsigned char));
  tools_gl2psListReset(list);

  index = 0;
  while(index < n) {
    len = 0;
    for(i = 0; i < 3; i++) {
      if(index < n){
        in[i] = buffer[index];
        len++;
      }
      else{
        in[i] = 0;
      }
      index++;
    }
    if(len) {
      tools_gl2psEncodeBase64Block(in, out, len);
      for(i = 0; i < 4; i++)
        tools_gl2psListAdd(list, &out[i]);
    }
  }
  tools_gl2psFree(buffer);
}

#endif

/* Helpers for rgba colors */

inline tools_GLboolean tools_gl2psSameColor(tools_GL2PSrgba rgba1, tools_GL2PSrgba rgba2)
{
  if(!TOOLS_GL2PS_ZERO(rgba1[0] - rgba2[0]) ||
     !TOOLS_GL2PS_ZERO(rgba1[1] - rgba2[1]) ||
     !TOOLS_GL2PS_ZERO(rgba1[2] - rgba2[2]))
    return TOOLS_GL_FALSE;
  return TOOLS_GL_TRUE;
}

inline tools_GLboolean tools_gl2psVertsSameColor(const tools_GL2PSprimitive *prim)
{
  int i;

  for(i = 1; i < prim->numverts; i++){
    if(!tools_gl2psSameColor(prim->verts[0].rgba, prim->verts[i].rgba)){
      return TOOLS_GL_FALSE;
    }
  }
  return TOOLS_GL_TRUE;
}

inline tools_GLboolean tools_gl2psSameColorThreshold(int n, tools_GL2PSrgba rgba[],
                                         tools_GL2PSrgba threshold)
{
  int i;

  if(n < 2) return TOOLS_GL_TRUE;

  for(i = 1; i < n; i++){
    if(fabs(rgba[0][0] - rgba[i][0]) > threshold[0] ||
       fabs(rgba[0][1] - rgba[i][1]) > threshold[1] ||
       fabs(rgba[0][2] - rgba[i][2]) > threshold[2])
      return TOOLS_GL_FALSE;
  }

  return TOOLS_GL_TRUE;
}

inline void tools_gl2psSetLastColor(tools_GL2PScontext* gl2ps, tools_GL2PSrgba rgba)
{
  int i;
  for(i = 0; i < 3; ++i){
    gl2ps->lastrgba[i] = rgba[i];
  }
}

inline tools_GLfloat tools_gl2psGetRGB(tools_GL2PSimage *im, tools_GLuint x, tools_GLuint y,
                           tools_GLfloat *red, tools_GLfloat *green, tools_GLfloat *blue)
{

  tools_GLsizei width = im->width;
  tools_GLsizei height = im->height;
  tools_GLfloat *pixels = im->pixels;
  tools_GLfloat *pimag;

  /* OpenGL image is from down to up, PS image is up to down */
  switch(im->format){
  case TOOLS_GL_RGBA:
    pimag = pixels + 4 * (width * (height - 1 - y) + x);
    break;
  case TOOLS_GL_RGB:
  default:
    pimag = pixels + 3 * (width * (height - 1 - y) + x);
    break;
  }
  *red = *pimag; pimag++;
  *green = *pimag; pimag++;
  *blue = *pimag; pimag++;

  return (im->format == TOOLS_GL_RGBA) ? *pimag : 1.0F;
}

/* Helper routines for pixmaps */

inline tools_GL2PSimage *tools_gl2psCopyPixmap(tools_GL2PSimage *im)
{
  int size;
  tools_GL2PSimage *image = (tools_GL2PSimage*)tools_gl2psMalloc(sizeof(tools_GL2PSimage));

  image->width = im->width;
  image->height = im->height;
  image->format = im->format;
  image->type = im->type;
  image->zoom_x = im->zoom_x;
  image->zoom_y = im->zoom_y;

  switch(image->format){
  case TOOLS_GL_RGBA:
    size = image->height * image->width * 4 * sizeof(tools_GLfloat);
    break;
  case TOOLS_GL_RGB:
  default:
    size = image->height * image->width * 3 * sizeof(tools_GLfloat);
    break;
  }

  image->pixels = (tools_GLfloat*)tools_gl2psMalloc(size);
  memcpy(image->pixels, im->pixels, size);

  return image;
}

inline void tools_gl2psFreePixmap(tools_GL2PSimage *im)
{
  if(!im)
    return;
  tools_gl2psFree(im->pixels);
  tools_gl2psFree(im);
}

#if defined(TOOLS_GL2PS_HAVE_LIBPNG)

#if !defined(png_jmpbuf)
#  define png_jmpbuf(png_ptr) ((png_ptr)->jmpbuf)
#endif

inline void tools_gl2psUserWritePNG(png_structp png_ptr, png_bytep data, png_size_t length)
{
  unsigned int i;
  tools_GL2PSlist *png = (tools_GL2PSlist*)png_get_io_ptr(png_ptr);
  for(i = 0; i < length; i++)
    tools_gl2psListAdd(png, &data[i]);
}

inline void tools_gl2psUserFlushPNG(png_structp png_ptr)
{
  (void) png_ptr;  /* not used */
}

inline void tools_gl2psConvertPixmapToPNG(tools_GL2PSimage *pixmap, tools_GL2PSlist *png)
{
  png_structp png_ptr;
  png_infop info_ptr;
  unsigned char *row_data;
  tools_GLfloat dr, dg, db;
  int row, col;

  if(!(png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL)))
    return;

  if(!(info_ptr = png_create_info_struct(png_ptr))){
    png_destroy_write_struct(&png_ptr, NULL);
    return;
  }

  if(setjmp(png_jmpbuf(png_ptr))) {
    png_destroy_write_struct(&png_ptr, &info_ptr);
    return;
  }

  png_set_write_fn(png_ptr, (void *)png, tools_gl2psUserWritePNG, tools_gl2psUserFlushPNG);
  png_set_compression_level(png_ptr, Z_DEFAULT_COMPRESSION);
  png_set_IHDR(png_ptr, info_ptr, pixmap->width, pixmap->height, 8,
               PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
               PNG_FILTER_TYPE_BASE);
  png_write_info(png_ptr, info_ptr);

  row_data = (unsigned char*)tools_gl2psMalloc(3 * pixmap->width * sizeof(unsigned char));
  for(row = 0; row < pixmap->height; row++){
    for(col = 0; col < pixmap->width; col++){
      tools_gl2psGetRGB(pixmap, col, row, &dr, &dg, &db);
      row_data[3*col] = (unsigned char)(255. * dr);
      row_data[3*col+1] = (unsigned char)(255. * dg);
      row_data[3*col+2] = (unsigned char)(255. * db);
    }
    png_write_row(png_ptr, (png_bytep)row_data);
  }
  tools_gl2psFree(row_data);

  png_write_end(png_ptr, info_ptr);
  png_destroy_write_struct(&png_ptr, &info_ptr);
}

#endif

/* Helper routines for text strings */

inline tools_GLint tools_gl2psAddText(tools_GL2PScontext* gl2ps, tools_GLint type, const char *str, const char *fontname,
                          tools_GLshort fontsize, tools_GLint alignment, tools_GLfloat angle,
                          tools_GL2PSrgba color, tools_GLboolean setblpos,
                          tools_GLfloat blx, tools_GLfloat bly)
{
  tools_GLfloat pos[4];
  tools_GL2PSprimitive *prim;
  tools_GLboolean valid;

  if(/*!gl2ps ||*/ !str || !fontname) return TOOLS_GL2PS_UNINITIALIZED;

  if(gl2ps->options & TOOLS_GL2PS_NO_TEXT) return TOOLS_GL2PS_SUCCESS;

  if (gl2ps->forcerasterpos) {
    pos[0] = gl2ps->rasterpos.xyz[0];
    pos[1] = gl2ps->rasterpos.xyz[1];
    pos[2] = gl2ps->rasterpos.xyz[2];
    pos[3] = 1.f;
  }
  else {
    tools_glGetBooleanv(TOOLS_GL_CURRENT_RASTER_POSITION_VALID, &valid);
    if(TOOLS_GL_FALSE == valid) return TOOLS_GL2PS_SUCCESS; /* the primitive is culled */
    tools_glGetFloatv(TOOLS_GL_CURRENT_RASTER_POSITION, pos);
  }

  prim = (tools_GL2PSprimitive*)tools_gl2psMalloc(sizeof(tools_GL2PSprimitive));
  prim->type = (tools_GLshort)type;
  prim->boundary = 0;
  prim->numverts = setblpos ? 2 : 1;
  prim->verts = (tools_GL2PSvertex*)tools_gl2psMalloc(sizeof(tools_GL2PSvertex) * prim->numverts);
  prim->verts[0].xyz[0] = pos[0];
  prim->verts[0].xyz[1] = pos[1];
  prim->verts[0].xyz[2] = pos[2];
  if (setblpos) {
    prim->verts[1].xyz[0] = blx;
    prim->verts[1].xyz[1] = bly;
    prim->verts[1].xyz[2] = 0;
  }
  prim->culled = 0;
  prim->offset = 0;
  prim->ofactor = 0.0;
  prim->ounits = 0.0;
  prim->pattern = 0;
  prim->factor = 0;
  prim->width = 1;
  prim->linecap = 0;
  prim->linejoin = 0;

  if (color) {
    memcpy(prim->verts[0].rgba, color, 4 * sizeof(float));
  }
  else {
    if (gl2ps->forcerasterpos) {
      prim->verts[0].rgba[0] = gl2ps->rasterpos.rgba[0];
      prim->verts[0].rgba[1] = gl2ps->rasterpos.rgba[1];
      prim->verts[0].rgba[2] = gl2ps->rasterpos.rgba[2];
      prim->verts[0].rgba[3] = gl2ps->rasterpos.rgba[3];
    }
    else {
      tools_glGetFloatv(TOOLS_GL_CURRENT_RASTER_COLOR, prim->verts[0].rgba);
    }
  }
  prim->data.text = (tools_GL2PSstring*)tools_gl2psMalloc(sizeof(tools_GL2PSstring));
  prim->data.text->str = (char*)tools_gl2psMalloc((strlen(str)+1)*sizeof(char));
  strcpy(prim->data.text->str, str);
  prim->data.text->fontname = (char*)tools_gl2psMalloc((strlen(fontname)+1)*sizeof(char));
  strcpy(prim->data.text->fontname, fontname);
  prim->data.text->fontsize = fontsize;
  prim->data.text->alignment = alignment;
  prim->data.text->angle = angle;

  gl2ps->forcerasterpos = TOOLS_GL_FALSE;

  /* If no OpenGL context, just add directly to primitives */
  if (gl2ps->options & TOOLS_GL2PS_NO_OPENGL_CONTEXT) {
    tools_gl2psListAdd(gl2ps->primitives, &prim);
  }
  else {
    tools_gl2psListAdd(gl2ps->auxprimitives, &prim);
    tools_glPassThrough(TOOLS_GL2PS_TEXT_TOKEN);
  }

  return TOOLS_GL2PS_SUCCESS;
}

inline tools_GL2PSstring *tools_gl2psCopyText(tools_GL2PSstring *t)
{
  tools_GL2PSstring *text = (tools_GL2PSstring*)tools_gl2psMalloc(sizeof(tools_GL2PSstring));
  text->str = (char*)tools_gl2psMalloc((strlen(t->str)+1)*sizeof(char));
  strcpy(text->str, t->str);
  text->fontname = (char*)tools_gl2psMalloc((strlen(t->fontname)+1)*sizeof(char));
  strcpy(text->fontname, t->fontname);
  text->fontsize = t->fontsize;
  text->alignment = t->alignment;
  text->angle = t->angle;

  return text;
}

inline void tools_gl2psFreeText(tools_GL2PSstring *text)
{
  if(!text)
    return;
  tools_gl2psFree(text->str);
  tools_gl2psFree(text->fontname);
  tools_gl2psFree(text);
}

/* Helpers for blending modes */

inline tools_GLboolean tools_gl2psSupportedBlendMode(tools_GLenum sfactor, tools_GLenum dfactor)
{
  /* returns TRUE if gl2ps supports the argument combination: only two
     blending modes have been implemented so far */

  if( (sfactor == TOOLS_GL_SRC_ALPHA && dfactor == TOOLS_GL_ONE_MINUS_SRC_ALPHA) ||
      (sfactor == TOOLS_GL_ONE && dfactor == TOOLS_GL_ZERO) )
    return TOOLS_GL_TRUE;
  return TOOLS_GL_FALSE;
}

inline void tools_gl2psAdaptVertexForBlending(tools_GL2PScontext* gl2ps, tools_GL2PSvertex *v)
{
  /* Transforms vertex depending on the actual blending function -
     currently the vertex v is considered as source vertex and his
     alpha value is changed to 1.0 if source blending TOOLS_GL_ONE is
     active. This might be extended in the future */

  if(!v /* || !gl2ps*/)
    return;

  if(gl2ps->options & TOOLS_GL2PS_NO_BLENDING || !gl2ps->blending){
    v->rgba[3] = 1.0F;
    return;
  }

  switch(gl2ps->blendfunc[0]){
  case TOOLS_GL_ONE:
    v->rgba[3] = 1.0F;
    break;
  default:
    break;
  }
}

inline void tools_gl2psAssignTriangleProperties(tools_GL2PStriangle *t)
{
  /* int i; */

  t->prop = T_VAR_COLOR;

  /* Uncommenting the following lines activates an even more fine
     grained distinction between triangle types - please don't delete,
     a remarkable amount of PDF handling code inside this file depends
     on it if activated */
  /*
  t->prop = T_CONST_COLOR;
  for(i = 0; i < 3; ++i){
    if(!TOOLS_GL2PS_ZERO(t->vertex[0].rgba[i] - t->vertex[1].rgba[i]) ||
       !TOOLS_GL2PS_ZERO(t->vertex[1].rgba[i] - t->vertex[2].rgba[i])){
      t->prop = T_VAR_COLOR;
      break;
    }
  }
  */

  if(!TOOLS_GL2PS_ZERO(t->vertex[0].rgba[3] - t->vertex[1].rgba[3]) ||
     !TOOLS_GL2PS_ZERO(t->vertex[1].rgba[3] - t->vertex[2].rgba[3])){
    t->prop |= T_VAR_ALPHA;
  }
  else{
    if(t->vertex[0].rgba[3] < 1)
      t->prop |= T_ALPHA_LESS_1;
    else
      t->prop |= T_ALPHA_1;
  }
}

inline void tools_gl2psFillTriangleFromPrimitive(tools_GL2PStriangle *t, tools_GL2PSprimitive *p,
                                           tools_GLboolean assignprops)
{
  t->vertex[0] = p->verts[0];
  t->vertex[1] = p->verts[1];
  t->vertex[2] = p->verts[2];
  if(TOOLS_GL_TRUE == assignprops)
    tools_gl2psAssignTriangleProperties(t);
}

inline void tools_gl2psInitTriangle(tools_GL2PStriangle *t)
{
  int i;
  tools_GL2PSvertex vertex = { {-1.0F, -1.0F, -1.0F}, {-1.0F, -1.0F, -1.0F, -1.0F} };
  for(i = 0; i < 3; i++)
    t->vertex[i] = vertex;
  t->prop = T_UNDEFINED;
}

/* Miscellaneous helper routines */

inline void tools_gl2psResetLineProperties(tools_GL2PScontext* gl2ps)
{
  gl2ps->lastlinewidth = 0.;
  gl2ps->lastlinecap = gl2ps->lastlinejoin = 0;
}

inline tools_GL2PSprimitive *tools_gl2psCopyPrimitive(tools_GL2PSprimitive *p)
{
  tools_GL2PSprimitive *prim;

  if(!p){
    tools_gl2psMsg(TOOLS_GL2PS_ERROR, "Trying to copy an empty primitive");
    return NULL;
  }

  prim = (tools_GL2PSprimitive*)tools_gl2psMalloc(sizeof(tools_GL2PSprimitive));

  prim->type = p->type;
  prim->numverts = p->numverts;
  prim->boundary = p->boundary;
  prim->offset = p->offset;
  prim->ofactor = p->ofactor;
  prim->ounits = p->ounits;
  prim->pattern = p->pattern;
  prim->factor = p->factor;
  prim->culled = p->culled;
  prim->width = p->width;
  prim->linecap = p->linecap;
  prim->linejoin = p->linejoin;
  prim->verts = (tools_GL2PSvertex*)tools_gl2psMalloc(p->numverts*sizeof(tools_GL2PSvertex));
  memcpy(prim->verts, p->verts, p->numverts * sizeof(tools_GL2PSvertex));

  switch(prim->type){
  case TOOLS_GL2PS_PIXMAP :
    prim->data.image = tools_gl2psCopyPixmap(p->data.image);
    break;
  case TOOLS_GL2PS_TEXT :
  case TOOLS_GL2PS_SPECIAL :
    prim->data.text = tools_gl2psCopyText(p->data.text);
    break;
  default:
    break;
  }

  return prim;
}

inline tools_GLboolean tools_gl2psSamePosition(tools_GL2PSxyz p1, tools_GL2PSxyz p2)
{
  if(!TOOLS_GL2PS_ZERO(p1[0] - p2[0]) ||
     !TOOLS_GL2PS_ZERO(p1[1] - p2[1]) ||
     !TOOLS_GL2PS_ZERO(p1[2] - p2[2]))
    return TOOLS_GL_FALSE;
  return TOOLS_GL_TRUE;
}

/*********************************************************************
 *
 * 3D sorting routines
 *
 *********************************************************************/

inline tools_GLfloat tools_gl2psComparePointPlane(tools_GL2PSxyz point, tools_GL2PSplane plane)
{
  return (plane[0] * point[0] +
          plane[1] * point[1] +
          plane[2] * point[2] +
          plane[3]);
}

inline tools_GLfloat tools_gl2psPsca(tools_GLfloat *a, tools_GLfloat *b)
{
  return (a[0]*b[0] + a[1]*b[1] + a[2]*b[2]);
}

inline void tools_gl2psPvec(tools_GLfloat *a, tools_GLfloat *b, tools_GLfloat *c)
{
  c[0] = a[1]*b[2] - a[2]*b[1];
  c[1] = a[2]*b[0] - a[0]*b[2];
  c[2] = a[0]*b[1] - a[1]*b[0];
}

inline tools_GLfloat tools_gl2psNorm(tools_GLfloat *a)
{
  return (tools_GLfloat)sqrt(a[0]*a[0] + a[1]*a[1] + a[2]*a[2]);
}

inline void tools_gl2psGetNormal(tools_GLfloat *a, tools_GLfloat *b, tools_GLfloat *c)
{
  tools_GLfloat norm;

  tools_gl2psPvec(a, b, c);
  if(!TOOLS_GL2PS_ZERO(norm = tools_gl2psNorm(c))){
    c[0] = c[0] / norm;
    c[1] = c[1] / norm;
    c[2] = c[2] / norm;
  }
  else{
    /* The plane is still wrong despite our tests in tools_gl2psGetPlane.
       Let's return a dummy value for now (this is a hack: we should
       do more intelligent tests in GetPlane) */
    c[0] = c[1] = 0.0F;
    c[2] = 1.0F;
  }
}

inline void tools_gl2psGetPlane(tools_GL2PSprimitive *prim, tools_GL2PSplane plane)
{
  tools_GL2PSxyz v = {0.0F, 0.0F, 0.0F}, w = {0.0F, 0.0F, 0.0F};

  switch(prim->type){
  case TOOLS_GL2PS_TRIANGLE :
  case TOOLS_GL2PS_QUADRANGLE :
    v[0] = prim->verts[1].xyz[0] - prim->verts[0].xyz[0];
    v[1] = prim->verts[1].xyz[1] - prim->verts[0].xyz[1];
    v[2] = prim->verts[1].xyz[2] - prim->verts[0].xyz[2];
    w[0] = prim->verts[2].xyz[0] - prim->verts[0].xyz[0];
    w[1] = prim->verts[2].xyz[1] - prim->verts[0].xyz[1];
    w[2] = prim->verts[2].xyz[2] - prim->verts[0].xyz[2];
    if((TOOLS_GL2PS_ZERO(v[0]) && TOOLS_GL2PS_ZERO(v[1]) && TOOLS_GL2PS_ZERO(v[2])) ||
       (TOOLS_GL2PS_ZERO(w[0]) && TOOLS_GL2PS_ZERO(w[1]) && TOOLS_GL2PS_ZERO(w[2]))){
      plane[0] = plane[1] = 0.0F;
      plane[2] = 1.0F;
      plane[3] = -prim->verts[0].xyz[2];
    }
    else{
      tools_gl2psGetNormal(v, w, plane);
      plane[3] =
        - plane[0] * prim->verts[0].xyz[0]
        - plane[1] * prim->verts[0].xyz[1]
        - plane[2] * prim->verts[0].xyz[2];
    }
    break;
  case TOOLS_GL2PS_LINE :
    v[0] = prim->verts[1].xyz[0] - prim->verts[0].xyz[0];
    v[1] = prim->verts[1].xyz[1] - prim->verts[0].xyz[1];
    v[2] = prim->verts[1].xyz[2] - prim->verts[0].xyz[2];
    if(TOOLS_GL2PS_ZERO(v[0]) && TOOLS_GL2PS_ZERO(v[1]) && TOOLS_GL2PS_ZERO(v[2])){
      plane[0] = plane[1] = 0.0F;
      plane[2] = 1.0F;
      plane[3] = -prim->verts[0].xyz[2];
    }
    else{
      if(TOOLS_GL2PS_ZERO(v[0]))      w[0] = 1.0F;
      else if(TOOLS_GL2PS_ZERO(v[1])) w[1] = 1.0F;
      else                      w[2] = 1.0F;
      tools_gl2psGetNormal(v, w, plane);
      plane[3] =
        - plane[0] * prim->verts[0].xyz[0]
        - plane[1] * prim->verts[0].xyz[1]
        - plane[2] * prim->verts[0].xyz[2];
    }
    break;
  case TOOLS_GL2PS_POINT :
  case TOOLS_GL2PS_PIXMAP :
  case TOOLS_GL2PS_TEXT :
  case TOOLS_GL2PS_SPECIAL :
  case TOOLS_GL2PS_IMAGEMAP:
    plane[0] = plane[1] = 0.0F;
    plane[2] = 1.0F;
    plane[3] = -prim->verts[0].xyz[2];
    break;
  default :
    tools_gl2psMsg(TOOLS_GL2PS_ERROR, "Unknown primitive type in BSP tree");
    plane[0] = plane[1] = plane[3] = 0.0F;
    plane[2] = 1.0F;
    break;
  }
}

inline void tools_gl2psCutEdge(tools_GL2PSvertex *a, tools_GL2PSvertex *b, tools_GL2PSplane plane,
                         tools_GL2PSvertex *c)
{
  tools_GL2PSxyz v;
  tools_GLfloat sect, psca;

  v[0] = b->xyz[0] - a->xyz[0];
  v[1] = b->xyz[1] - a->xyz[1];
  v[2] = b->xyz[2] - a->xyz[2];

  if(!TOOLS_GL2PS_ZERO(psca = tools_gl2psPsca(plane, v)))
    sect = -tools_gl2psComparePointPlane(a->xyz, plane) / psca;
  else
    sect = 0.0F;

  c->xyz[0] = a->xyz[0] + v[0] * sect;
  c->xyz[1] = a->xyz[1] + v[1] * sect;
  c->xyz[2] = a->xyz[2] + v[2] * sect;

  c->rgba[0] = (1 - sect) * a->rgba[0] + sect * b->rgba[0];
  c->rgba[1] = (1 - sect) * a->rgba[1] + sect * b->rgba[1];
  c->rgba[2] = (1 - sect) * a->rgba[2] + sect * b->rgba[2];
  c->rgba[3] = (1 - sect) * a->rgba[3] + sect * b->rgba[3];
}

inline void tools_gl2psCreateSplitPrimitive(tools_GL2PSprimitive *parent, tools_GL2PSplane plane,
                                      tools_GL2PSprimitive *child, tools_GLshort numverts,
                                      tools_GLshort *index0, tools_GLshort *index1)
{
  tools_GLshort i;

  if(parent->type == TOOLS_GL2PS_IMAGEMAP){
    child->type = TOOLS_GL2PS_IMAGEMAP;
    child->data.image = parent->data.image;
  }
  else{
    if(numverts > 4){
      tools_gl2psMsg(TOOLS_GL2PS_WARNING, "%d vertices in polygon", numverts);
      numverts = 4;
    }
    switch(numverts){
    case 1 : child->type = TOOLS_GL2PS_POINT; break;
    case 2 : child->type = TOOLS_GL2PS_LINE; break;
    case 3 : child->type = TOOLS_GL2PS_TRIANGLE; break;
    case 4 : child->type = TOOLS_GL2PS_QUADRANGLE; break;
    default: child->type = TOOLS_GL2PS_NO_TYPE; break;
    }
  }

  child->boundary = 0; /* FIXME: not done! */
  child->culled = parent->culled;
  child->offset = parent->offset;
  child->ofactor = parent->ofactor;
  child->ounits = parent->ounits;
  child->pattern = parent->pattern;
  child->factor = parent->factor;
  child->width = parent->width;
  child->linecap = parent->linecap;
  child->linejoin = parent->linejoin;
  child->numverts = numverts;
  child->verts = (tools_GL2PSvertex*)tools_gl2psMalloc(numverts * sizeof(tools_GL2PSvertex));

  for(i = 0; i < numverts; i++){
    if(index1[i] < 0){
      child->verts[i] = parent->verts[index0[i]];
    }
    else{
      tools_gl2psCutEdge(&parent->verts[index0[i]], &parent->verts[index1[i]],
                   plane, &child->verts[i]);
    }
  }
}

inline void tools_gl2psAddIndex(tools_GLshort *index0, tools_GLshort *index1, tools_GLshort *nb,
                          tools_GLshort i, tools_GLshort j)
{
  tools_GLint k;

  for(k = 0; k < *nb; k++){
    if((index0[k] == i && index1[k] == j) ||
       (index1[k] == i && index0[k] == j)) return;
  }
  index0[*nb] = i;
  index1[*nb] = j;
  (*nb)++;
}

inline tools_GLshort tools_gl2psGetIndex(tools_GLshort i, tools_GLshort num)
{
  return (i < num - 1) ? i + 1 : 0;
}

inline tools_GLint tools_gl2psTestSplitPrimitive(tools_GL2PSprimitive *prim, tools_GL2PSplane plane)
{
  tools_GLint type = TOOLS_GL2PS_COINCIDENT;
  tools_GLshort i, j;
  tools_GLfloat d[5];

  for(i = 0; i < prim->numverts; i++){
    d[i] = tools_gl2psComparePointPlane(prim->verts[i].xyz, plane);
  }

  if(prim->numverts < 2){
    return 0;
  }
  else{
    for(i = 0; i < prim->numverts; i++){
      j = tools_gl2psGetIndex(i, prim->numverts);
      if(d[j] > TOOLS_GL2PS_EPSILON){
        if(type == TOOLS_GL2PS_COINCIDENT)      type = TOOLS_GL2PS_IN_BACK_OF;
        else if(type != TOOLS_GL2PS_IN_BACK_OF) return 1;
        if(d[i] < -TOOLS_GL2PS_EPSILON)         return 1;
      }
      else if(d[j] < -TOOLS_GL2PS_EPSILON){
        if(type == TOOLS_GL2PS_COINCIDENT)       type = TOOLS_GL2PS_IN_FRONT_OF;
        else if(type != TOOLS_GL2PS_IN_FRONT_OF) return 1;
        if(d[i] > TOOLS_GL2PS_EPSILON)           return 1;
      }
    }
  }
  return 0;
}

inline tools_GLint tools_gl2psSplitPrimitive(tools_GL2PSprimitive *prim, tools_GL2PSplane plane,
                                 tools_GL2PSprimitive **front, tools_GL2PSprimitive **back)
{
  tools_GLshort i, j, in = 0, out = 0, in0[5], in1[5], out0[5], out1[5];
  tools_GLint type;
  tools_GLfloat d[5] = {0.0};

  type = TOOLS_GL2PS_COINCIDENT;

  for(i = 0; i < prim->numverts; i++){
    d[i] = tools_gl2psComparePointPlane(prim->verts[i].xyz, plane);
  }

  switch(prim->type){
  case TOOLS_GL2PS_POINT :
    if(d[0] > TOOLS_GL2PS_EPSILON)       type = TOOLS_GL2PS_IN_BACK_OF;
    else if(d[0] < -TOOLS_GL2PS_EPSILON) type = TOOLS_GL2PS_IN_FRONT_OF;
    else                           type = TOOLS_GL2PS_COINCIDENT;
    break;
  default :
    for(i = 0; i < prim->numverts; i++){
      j = tools_gl2psGetIndex(i, prim->numverts);
      if(d[j] > TOOLS_GL2PS_EPSILON){
        if(type == TOOLS_GL2PS_COINCIDENT)      type = TOOLS_GL2PS_IN_BACK_OF;
        else if(type != TOOLS_GL2PS_IN_BACK_OF) type = TOOLS_GL2PS_SPANNING;
        if(d[i] < -TOOLS_GL2PS_EPSILON){
          tools_gl2psAddIndex(in0, in1, &in, i, j);
          tools_gl2psAddIndex(out0, out1, &out, i, j);
          type = TOOLS_GL2PS_SPANNING;
        }
        tools_gl2psAddIndex(out0, out1, &out, j, -1);
      }
      else if(d[j] < -TOOLS_GL2PS_EPSILON){
        if(type == TOOLS_GL2PS_COINCIDENT)       type = TOOLS_GL2PS_IN_FRONT_OF;
        else if(type != TOOLS_GL2PS_IN_FRONT_OF) type = TOOLS_GL2PS_SPANNING;
        if(d[i] > TOOLS_GL2PS_EPSILON){
          tools_gl2psAddIndex(in0, in1, &in, i, j);
          tools_gl2psAddIndex(out0, out1, &out, i, j);
          type = TOOLS_GL2PS_SPANNING;
        }
        tools_gl2psAddIndex(in0, in1, &in, j, -1);
      }
      else{
        tools_gl2psAddIndex(in0, in1, &in, j, -1);
        tools_gl2psAddIndex(out0, out1, &out, j, -1);
      }
    }
    break;
  }

  if(type == TOOLS_GL2PS_SPANNING){
    *back = (tools_GL2PSprimitive*)tools_gl2psMalloc(sizeof(tools_GL2PSprimitive));
    *front = (tools_GL2PSprimitive*)tools_gl2psMalloc(sizeof(tools_GL2PSprimitive));
    tools_gl2psCreateSplitPrimitive(prim, plane, *back, out, out0, out1);
    tools_gl2psCreateSplitPrimitive(prim, plane, *front, in, in0, in1);
  }

  return type;
}

inline void tools_gl2psDivideQuad(tools_GL2PSprimitive *quad,
                            tools_GL2PSprimitive **t1, tools_GL2PSprimitive **t2)
{
  *t1 = (tools_GL2PSprimitive*)tools_gl2psMalloc(sizeof(tools_GL2PSprimitive));
  *t2 = (tools_GL2PSprimitive*)tools_gl2psMalloc(sizeof(tools_GL2PSprimitive));
  (*t1)->type = (*t2)->type = TOOLS_GL2PS_TRIANGLE;
  (*t1)->numverts = (*t2)->numverts = 3;
  (*t1)->culled = (*t2)->culled = quad->culled;
  (*t1)->offset = (*t2)->offset = quad->offset;
  (*t1)->ofactor = (*t2)->ofactor = quad->ofactor;
  (*t1)->ounits = (*t2)->ounits = quad->ounits;
  (*t1)->pattern = (*t2)->pattern = quad->pattern;
  (*t1)->factor = (*t2)->factor = quad->factor;
  (*t1)->width = (*t2)->width = quad->width;
  (*t1)->linecap = (*t2)->linecap = quad->linecap;
  (*t1)->linejoin = (*t2)->linejoin = quad->linejoin;
  (*t1)->verts = (tools_GL2PSvertex*)tools_gl2psMalloc(3 * sizeof(tools_GL2PSvertex));
  (*t2)->verts = (tools_GL2PSvertex*)tools_gl2psMalloc(3 * sizeof(tools_GL2PSvertex));
  (*t1)->verts[0] = quad->verts[0];
  (*t1)->verts[1] = quad->verts[1];
  (*t1)->verts[2] = quad->verts[2];
  (*t1)->boundary = ((quad->boundary & 1) ? 1 : 0) | ((quad->boundary & 2) ? 2 : 0);
  (*t2)->verts[0] = quad->verts[0];
  (*t2)->verts[1] = quad->verts[2];
  (*t2)->verts[2] = quad->verts[3];
  (*t2)->boundary = ((quad->boundary & 4) ? 2 : 0) | ((quad->boundary & 8) ? 4 : 0);
}

inline int tools_gl2psCompareDepth(const void *a, const void *b)
{
  const tools_GL2PSprimitive *q, *w;
  tools_GLfloat dq = 0.0F, dw = 0.0F, diff;
  int i;

  q = *(const tools_GL2PSprimitive* const*)a;
  w = *(const tools_GL2PSprimitive* const*)b;

  for(i = 0; i < q->numverts; i++){
    dq += q->verts[i].xyz[2];
  }
  dq /= (tools_GLfloat)q->numverts;

  for(i = 0; i < w->numverts; i++){
    dw += w->verts[i].xyz[2];
  }
  dw /= (tools_GLfloat)w->numverts;

  diff = dq - dw;
  if(diff > 0.){
    return -1;
  }
  else if(diff < 0.){
    return 1;
  }
  else{
    /* Ensure that initial ordering is preserved when depths match. */
    if(q->sortid==w->sortid) return 0;  //G.Barrand.
    return q->sortid < w->sortid ? -1 : 1;
  }
}

inline int tools_gl2psTrianglesFirst(const void *a, const void *b)
{
  const tools_GL2PSprimitive *q, *w;

  q = *(const tools_GL2PSprimitive* const*)a;
  w = *(const tools_GL2PSprimitive* const*)b;
  if(q->type==w->type) return 0;  //G.Barrand.
  return (q->type < w->type ? 1 : -1);
}

inline tools_GLint tools_gl2psFindRoot(tools_GL2PScontext* gl2ps, tools_GL2PSlist *primitives, tools_GL2PSprimitive **root)
{
  tools_GLint i, j, count, best = 1000000, idx = 0;
  tools_GL2PSprimitive *prim1, *prim2;
  tools_GL2PSplane plane;
  tools_GLint maxp;

  if(!tools_gl2psListNbr(primitives)){
    tools_gl2psMsg(TOOLS_GL2PS_ERROR, "Cannot fint root in empty primitive list");
    *root = 0; /*G.Barrand: to quiet gcc.*/
    return 0;
  }

  *root = *(tools_GL2PSprimitive**)tools_gl2psListPointer(primitives, 0);

  if(gl2ps->options & TOOLS_GL2PS_BEST_ROOT){
    maxp = tools_gl2psListNbr(primitives);
    if(maxp > gl2ps->maxbestroot){
      maxp = gl2ps->maxbestroot;
    }
    for(i = 0; i < maxp; i++){
      prim1 = *(tools_GL2PSprimitive**)tools_gl2psListPointer(primitives, i);
      tools_gl2psGetPlane(prim1, plane);
      count = 0;
      for(j = 0; j < tools_gl2psListNbr(primitives); j++){
        if(j != i){
          prim2 = *(tools_GL2PSprimitive**)tools_gl2psListPointer(primitives, j);
          count += tools_gl2psTestSplitPrimitive(prim2, plane);
        }
        if(count > best) break;
      }
      if(count < best){
        best = count;
        idx = i;
        *root = prim1;
        if(!count) return idx;
      }
    }
    /* if(index) tools_gl2psMsg(TOOLS_GL2PS_INFO, "TOOLS_GL2PS_BEST_ROOT was worth it: %d", index); */
    return idx;
  }
  else{
    return 0;
  }
}

inline void tools_gl2psFreeImagemap(tools_GL2PSimagemap *list)
{
  tools_GL2PSimagemap *next;
  while(list != NULL){
    next = list->next;
    tools_gl2psFree(list->image->pixels);
    tools_gl2psFree(list->image);
    tools_gl2psFree(list);
    list = next;
  }
}

inline void tools_gl2psFreePrimitive(void *data)
{
  tools_GL2PSprimitive *q;

  q = *(tools_GL2PSprimitive**)data;
  tools_gl2psFree(q->verts);
  if(q->type == TOOLS_GL2PS_TEXT || q->type == TOOLS_GL2PS_SPECIAL){
    tools_gl2psFreeText(q->data.text);
  }
  else if(q->type == TOOLS_GL2PS_PIXMAP){
    tools_gl2psFreePixmap(q->data.image);
  }
  tools_gl2psFree(q);
}

inline void tools_gl2psAddPrimitiveInList(tools_GL2PSprimitive *prim, tools_GL2PSlist *list)
{
  tools_GL2PSprimitive *t1, *t2;

  if(prim->type != TOOLS_GL2PS_QUADRANGLE){
    tools_gl2psListAdd(list, &prim);
  }
  else{
    tools_gl2psDivideQuad(prim, &t1, &t2);
    tools_gl2psListAdd(list, &t1);
    tools_gl2psListAdd(list, &t2);
    tools_gl2psFreePrimitive(&prim);
  }

}

inline void tools_gl2psFreeBspTree(tools_GL2PSbsptree **tree)
{
  if(*tree){
    if((*tree)->back) tools_gl2psFreeBspTree(&(*tree)->back);
    if((*tree)->primitives){
      tools_gl2psListAction((*tree)->primitives, tools_gl2psFreePrimitive);
      tools_gl2psListDelete((*tree)->primitives);
    }
    if((*tree)->front) tools_gl2psFreeBspTree(&(*tree)->front);
    tools_gl2psFree(*tree);
    *tree = NULL;
  }
}

inline tools_GLboolean tools_gl2psGreater(tools_GLfloat f1, tools_GLfloat f2)
{
  if(f1 > f2) return TOOLS_GL_TRUE;
  else return TOOLS_GL_FALSE;
}

inline tools_GLboolean tools_gl2psLess(tools_GLfloat f1, tools_GLfloat f2)
{
  if(f1 < f2) return TOOLS_GL_TRUE;
  else return TOOLS_GL_FALSE;
}

inline void tools_gl2psBuildBspTree(tools_GL2PScontext* gl2ps, tools_GL2PSbsptree *tree, tools_GL2PSlist *primitives)
{
  tools_GL2PSprimitive *prim = NULL, *frontprim = NULL, *backprim = NULL;
  tools_GL2PSlist *frontlist, *backlist;
  tools_GLint i, idx;

  tree->front = NULL;
  tree->back = NULL;
  tree->primitives = tools_gl2psListCreate(1, 2, sizeof(tools_GL2PSprimitive*));
  idx = tools_gl2psFindRoot(gl2ps, primitives, &prim);
  tools_gl2psGetPlane(prim, tree->plane);
  tools_gl2psAddPrimitiveInList(prim, tree->primitives);

  frontlist = tools_gl2psListCreate(1, 2, sizeof(tools_GL2PSprimitive*));
  backlist = tools_gl2psListCreate(1, 2, sizeof(tools_GL2PSprimitive*));

  for(i = 0; i < tools_gl2psListNbr(primitives); i++){
    if(i != idx){
      prim = *(tools_GL2PSprimitive**)tools_gl2psListPointer(primitives,i);
      switch(tools_gl2psSplitPrimitive(prim, tree->plane, &frontprim, &backprim)){
      case TOOLS_GL2PS_COINCIDENT:
        tools_gl2psAddPrimitiveInList(prim, tree->primitives);
        break;
      case TOOLS_GL2PS_IN_BACK_OF:
        tools_gl2psAddPrimitiveInList(prim, backlist);
        break;
      case TOOLS_GL2PS_IN_FRONT_OF:
        tools_gl2psAddPrimitiveInList(prim, frontlist);
        break;
      case TOOLS_GL2PS_SPANNING:
        tools_gl2psAddPrimitiveInList(backprim, backlist);
        tools_gl2psAddPrimitiveInList(frontprim, frontlist);
        tools_gl2psFreePrimitive(&prim);
        break;
      }
    }
  }

  if(tools_gl2psListNbr(tree->primitives)){
    tools_gl2psListSort(gl2ps, tree->primitives, tools_gl2psTrianglesFirst);
  }

  if(tools_gl2psListNbr(frontlist)){
    tools_gl2psListSort(gl2ps, frontlist, tools_gl2psTrianglesFirst);
    tree->front = (tools_GL2PSbsptree*)tools_gl2psMalloc(sizeof(tools_GL2PSbsptree));
    tools_gl2psBuildBspTree(gl2ps, tree->front, frontlist);
  }
  else{
    tools_gl2psListDelete(frontlist);
  }

  if(tools_gl2psListNbr(backlist)){
    tools_gl2psListSort(gl2ps, backlist, tools_gl2psTrianglesFirst);
    tree->back = (tools_GL2PSbsptree*)tools_gl2psMalloc(sizeof(tools_GL2PSbsptree));
    tools_gl2psBuildBspTree(gl2ps, tree->back, backlist);
  }
  else{
    tools_gl2psListDelete(backlist);
  }

  tools_gl2psListDelete(primitives);
}

inline void tools_gl2psTraverseBspTree(tools_GL2PScontext* gl2ps, tools_GL2PSbsptree *tree, tools_GL2PSxyz eye, tools_GLfloat epsilon,
                                 tools_GLboolean (*compare)(tools_GLfloat f1, tools_GLfloat f2),
                                 void (*action)(tools_GL2PScontext*, void *data), int inverse)
{
  tools_GLfloat result;

  if(!tree) return;

  result = tools_gl2psComparePointPlane(eye, tree->plane);

  if(TOOLS_GL_TRUE == compare(result, epsilon)){
    tools_gl2psTraverseBspTree(gl2ps, tree->back, eye, epsilon, compare, action, inverse);
    if(inverse){
      tools_gl2psListActionInverseContext(gl2ps, tree->primitives, action);
    }
    else{
      tools_gl2psListActionContext(gl2ps, tree->primitives, action);
    }
    tools_gl2psTraverseBspTree(gl2ps, tree->front, eye, epsilon, compare, action, inverse);
  }
  else if(TOOLS_GL_TRUE == compare(-epsilon, result)){
    tools_gl2psTraverseBspTree(gl2ps, tree->front, eye, epsilon, compare, action, inverse);
    if(inverse){
      tools_gl2psListActionInverseContext(gl2ps, tree->primitives, action);
    }
    else{
      tools_gl2psListActionContext(gl2ps, tree->primitives, action);
    }
    tools_gl2psTraverseBspTree(gl2ps, tree->back, eye, epsilon, compare, action, inverse);
  }
  else{
    tools_gl2psTraverseBspTree(gl2ps, tree->front, eye, epsilon, compare, action, inverse);
    tools_gl2psTraverseBspTree(gl2ps, tree->back, eye, epsilon, compare, action, inverse);
  }
}

inline void tools_gl2psRescaleAndOffset(tools_GL2PScontext* gl2ps)
{
  tools_GL2PSprimitive *prim;
  tools_GLfloat minZ, maxZ, rangeZ, scaleZ;
  tools_GLfloat factor, units, area, dZ, dZdX, dZdY, maxdZ;
  int i, j;

  if(!tools_gl2psListNbr(gl2ps->primitives))
    return;

  /* get z-buffer range */
  prim = *(tools_GL2PSprimitive**)tools_gl2psListPointer(gl2ps->primitives, 0);
  minZ = maxZ = prim->verts[0].xyz[2];
  for(i = 1; i < prim->numverts; i++){
    if(prim->verts[i].xyz[2] < minZ) minZ = prim->verts[i].xyz[2];
    if(prim->verts[i].xyz[2] > maxZ) maxZ = prim->verts[i].xyz[2];
  }
  for(i = 1; i < tools_gl2psListNbr(gl2ps->primitives); i++){
    prim = *(tools_GL2PSprimitive**)tools_gl2psListPointer(gl2ps->primitives, i);
    for(j = 0; j < prim->numverts; j++){
      if(prim->verts[j].xyz[2] < minZ) minZ = prim->verts[j].xyz[2];
      if(prim->verts[j].xyz[2] > maxZ) maxZ = prim->verts[j].xyz[2];
    }
  }
  rangeZ = (maxZ - minZ);

  /* rescale z-buffer coordinate in [0,TOOLS_GL2PS_ZSCALE], to make it of
     the same order of magnitude as the x and y coordinates */
  scaleZ = TOOLS_GL2PS_ZERO(rangeZ) ? TOOLS_GL2PS_ZSCALE : (TOOLS_GL2PS_ZSCALE / rangeZ);
  /* avoid precision loss (we use floats!) */
  if(scaleZ > 100000.F) scaleZ = 100000.F;

  /* apply offsets */
  for(i = 0; i < tools_gl2psListNbr(gl2ps->primitives); i++){
    prim = *(tools_GL2PSprimitive**)tools_gl2psListPointer(gl2ps->primitives, i);
    for(j = 0; j < prim->numverts; j++){
      prim->verts[j].xyz[2] = (prim->verts[j].xyz[2] - minZ) * scaleZ;
    }
    if((gl2ps->options & TOOLS_GL2PS_SIMPLE_LINE_OFFSET) &&
       (prim->type == TOOLS_GL2PS_LINE)){
      if(gl2ps->sort == TOOLS_GL2PS_SIMPLE_SORT){
        prim->verts[0].xyz[2] -= TOOLS_GL2PS_ZOFFSET_LARGE;
        prim->verts[1].xyz[2] -= TOOLS_GL2PS_ZOFFSET_LARGE;
      }
      else{
        prim->verts[0].xyz[2] -= TOOLS_GL2PS_ZOFFSET;
        prim->verts[1].xyz[2] -= TOOLS_GL2PS_ZOFFSET;
      }
    }
    else if(prim->offset && (prim->type == TOOLS_GL2PS_TRIANGLE)){
      factor = prim->ofactor;
      units = prim->ounits;
      area =
        (prim->verts[1].xyz[0] - prim->verts[0].xyz[0]) *
        (prim->verts[2].xyz[1] - prim->verts[1].xyz[1]) -
        (prim->verts[2].xyz[0] - prim->verts[1].xyz[0]) *
        (prim->verts[1].xyz[1] - prim->verts[0].xyz[1]);
      if(!TOOLS_GL2PS_ZERO(area)){
        dZdX =
          ((prim->verts[2].xyz[1] - prim->verts[1].xyz[1]) *
           (prim->verts[1].xyz[2] - prim->verts[0].xyz[2]) -
           (prim->verts[1].xyz[1] - prim->verts[0].xyz[1]) *
           (prim->verts[2].xyz[2] - prim->verts[1].xyz[2])) / area;
        dZdY =
          ((prim->verts[1].xyz[0] - prim->verts[0].xyz[0]) *
           (prim->verts[2].xyz[2] - prim->verts[1].xyz[2]) -
           (prim->verts[2].xyz[0] - prim->verts[1].xyz[0]) *
           (prim->verts[1].xyz[2] - prim->verts[0].xyz[2])) / area;
        maxdZ = (tools_GLfloat)sqrt(dZdX * dZdX + dZdY * dZdY);
      }
      else{
        maxdZ = 0.0F;
      }
      dZ = factor * maxdZ + units;
      prim->verts[0].xyz[2] += dZ;
      prim->verts[1].xyz[2] += dZ;
      prim->verts[2].xyz[2] += dZ;
    }
  }
}

/*********************************************************************
 *
 * 2D sorting routines (for occlusion culling)
 *
 *********************************************************************/

inline tools_GLint tools_gl2psGetPlaneFromPoints(tools_GL2PSxyz a, tools_GL2PSxyz b, tools_GL2PSplane plane)
{
  tools_GLfloat n;

  plane[0] = b[1] - a[1];
  plane[1] = a[0] - b[0];
  n = (tools_GLfloat)sqrt(plane[0]*plane[0] + plane[1]*plane[1]);
  plane[2] = 0.0F;
  if(!TOOLS_GL2PS_ZERO(n)){
    plane[0] /= n;
    plane[1] /= n;
    plane[3] = -plane[0]*a[0]-plane[1]*a[1];
    return 1;
  }
  else{
    plane[0] = -1.0F;
    plane[1] = 0.0F;
    plane[3] = a[0];
    return 0;
  }
}

inline void tools_gl2psFreeBspImageTree(tools_GL2PSbsptree2d **tree)
{
  if(*tree){
    if((*tree)->back)  tools_gl2psFreeBspImageTree(&(*tree)->back);
    if((*tree)->front) tools_gl2psFreeBspImageTree(&(*tree)->front);
    tools_gl2psFree(*tree);
    *tree = NULL;
  }
}

inline tools_GLint tools_gl2psCheckPoint(tools_GL2PSxyz point, tools_GL2PSplane plane)
{
  tools_GLfloat pt_dis;

  pt_dis = tools_gl2psComparePointPlane(point, plane);
  if(pt_dis > TOOLS_GL2PS_EPSILON)        return TOOLS_GL2PS_POINT_INFRONT;
  else if(pt_dis < -TOOLS_GL2PS_EPSILON)  return TOOLS_GL2PS_POINT_BACK;
  else                              return TOOLS_GL2PS_POINT_COINCIDENT;
}

inline void tools_gl2psAddPlanesInBspTreeImage(tools_GL2PSprimitive *prim,
                                         tools_GL2PSbsptree2d **tree)
{
  tools_GLint ret = 0;
  tools_GLint i;
  tools_GLint offset = 0;
  tools_GL2PSbsptree2d *head = NULL, *cur = NULL;

  if((*tree == NULL) && (prim->numverts > 2)){
    /* don't cull if transparent
    for(i = 0; i < prim->numverts - 1; i++)
      if(prim->verts[i].rgba[3] < 1.0F) return;
    */
    head = (tools_GL2PSbsptree2d*)tools_gl2psMalloc(sizeof(tools_GL2PSbsptree2d));
    for(i = 0; i < prim->numverts-1; i++){
      if(!tools_gl2psGetPlaneFromPoints(prim->verts[i].xyz,
                                  prim->verts[i+1].xyz,
                                  head->plane)){
        if(prim->numverts-i > 3){
          offset++;
        }
        else{
          tools_gl2psFree(head);
          return;
        }
      }
      else{
        break;
      }
    }
    head->back = NULL;
    head->front = NULL;
    for(i = 2+offset; i < prim->numverts; i++){
      ret = tools_gl2psCheckPoint(prim->verts[i].xyz, head->plane);
      if(ret != TOOLS_GL2PS_POINT_COINCIDENT) break;
    }
    switch(ret){
    case TOOLS_GL2PS_POINT_INFRONT :
      cur = head;
      for(i = 1+offset; i < prim->numverts-1; i++){
        if(cur->front == NULL){
          cur->front = (tools_GL2PSbsptree2d*)tools_gl2psMalloc(sizeof(tools_GL2PSbsptree2d));
        }
        if(tools_gl2psGetPlaneFromPoints(prim->verts[i].xyz,
                                   prim->verts[i+1].xyz,
                                   cur->front->plane)){
          cur = cur->front;
          cur->front = NULL;
          cur->back = NULL;
        }
      }
      if(cur->front == NULL){
        cur->front = (tools_GL2PSbsptree2d*)tools_gl2psMalloc(sizeof(tools_GL2PSbsptree2d));
      }
      if(tools_gl2psGetPlaneFromPoints(prim->verts[i].xyz,
                                 prim->verts[offset].xyz,
                                 cur->front->plane)){
        cur->front->front = NULL;
        cur->front->back = NULL;
      }
      else{
        tools_gl2psFree(cur->front);
        cur->front = NULL;
      }
      break;
    case TOOLS_GL2PS_POINT_BACK :
      for(i = 0; i < 4; i++){
        head->plane[i] = -head->plane[i];
      }
      cur = head;
      for(i = 1+offset; i < prim->numverts-1; i++){
        if(cur->front == NULL){
          cur->front = (tools_GL2PSbsptree2d*)tools_gl2psMalloc(sizeof(tools_GL2PSbsptree2d));
        }
        if(tools_gl2psGetPlaneFromPoints(prim->verts[i+1].xyz,
                                   prim->verts[i].xyz,
                                   cur->front->plane)){
          cur = cur->front;
          cur->front = NULL;
          cur->back = NULL;
        }
      }
      if(cur->front == NULL){
        cur->front = (tools_GL2PSbsptree2d*)tools_gl2psMalloc(sizeof(tools_GL2PSbsptree2d));
      }
      if(tools_gl2psGetPlaneFromPoints(prim->verts[offset].xyz,
                                 prim->verts[i].xyz,
                                 cur->front->plane)){
        cur->front->front = NULL;
        cur->front->back = NULL;
      }
      else{
        tools_gl2psFree(cur->front);
        cur->front = NULL;
      }
      break;
    default:
      tools_gl2psFree(head);
      return;
    }
    (*tree) = head;
  }
}

inline tools_GLint tools_gl2psCheckPrimitive(tools_GL2PSprimitive *prim, tools_GL2PSplane plane)
{
  tools_GLint i;
  tools_GLint pos;

  pos = tools_gl2psCheckPoint(prim->verts[0].xyz, plane);
  for(i = 1; i < prim->numverts; i++){
    pos |= tools_gl2psCheckPoint(prim->verts[i].xyz, plane);
    if(pos == (TOOLS_GL2PS_POINT_INFRONT | TOOLS_GL2PS_POINT_BACK)) return TOOLS_GL2PS_SPANNING;
  }
  if(pos & TOOLS_GL2PS_POINT_INFRONT)   return TOOLS_GL2PS_IN_FRONT_OF;
  else if(pos & TOOLS_GL2PS_POINT_BACK) return TOOLS_GL2PS_IN_BACK_OF;
  else                            return TOOLS_GL2PS_COINCIDENT;
}

inline tools_GL2PSprimitive *tools_gl2psCreateSplitPrimitive2D(tools_GL2PSprimitive *parent,
                                                   tools_GLshort numverts,
                                                   tools_GL2PSvertex *vertx)
{
  tools_GLint i;
  tools_GL2PSprimitive *child = (tools_GL2PSprimitive*)tools_gl2psMalloc(sizeof(tools_GL2PSprimitive));

  if(parent->type == TOOLS_GL2PS_IMAGEMAP){
    child->type = TOOLS_GL2PS_IMAGEMAP;
    child->data.image = parent->data.image;
  }
  else {
    switch(numverts){
    case 1 : child->type = TOOLS_GL2PS_POINT; break;
    case 2 : child->type = TOOLS_GL2PS_LINE; break;
    case 3 : child->type = TOOLS_GL2PS_TRIANGLE; break;
    case 4 : child->type = TOOLS_GL2PS_QUADRANGLE; break;
    default: child->type = TOOLS_GL2PS_NO_TYPE; break; /* FIXME */
    }
  }
  child->boundary = 0; /* FIXME: not done! */
  child->culled = parent->culled;
  child->offset = parent->offset;
  child->ofactor = parent->ofactor;
  child->ounits = parent->ounits;
  child->pattern = parent->pattern;
  child->factor = parent->factor;
  child->width = parent->width;
  child->linecap = parent->linecap;
  child->linejoin = parent->linejoin;
  child->numverts = numverts;
  child->verts = (tools_GL2PSvertex*)tools_gl2psMalloc(numverts * sizeof(tools_GL2PSvertex));
  for(i = 0; i < numverts; i++){
    child->verts[i] = vertx[i];
  }
  return child;
}

inline void tools_gl2psSplitPrimitive2D(tools_GL2PSprimitive *prim,
                                  tools_GL2PSplane plane,
                                  tools_GL2PSprimitive **front,
                                  tools_GL2PSprimitive **back)
{
  /* cur will hold the position of the current vertex
     prev will hold the position of the previous vertex
     prev0 will hold the position of the vertex number 0
     v1 and v2 represent the current and previous vertices, respectively
     flag is set if the current vertex should be checked against the plane */
  tools_GLint cur = -1, prev = -1, i, v1 = 0, v2 = 0, flag = 1, prev0 = -1;

  /* list of vertices that will go in front and back primitive */
  tools_GL2PSvertex *front_list = NULL, *back_list = NULL;

  /* number of vertices in front and back list */
  tools_GLshort front_count = 0, back_count = 0;

  for(i = 0; i <= prim->numverts; i++){
    v1 = i;
    if(v1 == prim->numverts){
      if(prim->numverts < 3) break;
      v1 = 0;
      v2 = prim->numverts - 1;
      cur = prev0;
    }
    else if(flag){
      cur = tools_gl2psCheckPoint(prim->verts[v1].xyz, plane);
      if(i == 0){
        prev0 = cur;
      }
    }
    if(((prev == -1) || (prev == cur) || (prev == 0) || (cur == 0)) &&
       (i < prim->numverts)){
      if(cur == TOOLS_GL2PS_POINT_INFRONT){
        front_count++;
        front_list = (tools_GL2PSvertex*)tools_gl2psRealloc(front_list,
                                                sizeof(tools_GL2PSvertex)*front_count);
        front_list[front_count-1] = prim->verts[v1];
      }
      else if(cur == TOOLS_GL2PS_POINT_BACK){
        back_count++;
        back_list = (tools_GL2PSvertex*)tools_gl2psRealloc(back_list,
                                               sizeof(tools_GL2PSvertex)*back_count);
        back_list[back_count-1] = prim->verts[v1];
      }
      else{
        front_count++;
        front_list = (tools_GL2PSvertex*)tools_gl2psRealloc(front_list,
                                                sizeof(tools_GL2PSvertex)*front_count);
        front_list[front_count-1] = prim->verts[v1];
        back_count++;
        back_list = (tools_GL2PSvertex*)tools_gl2psRealloc(back_list,
                                               sizeof(tools_GL2PSvertex)*back_count);
        back_list[back_count-1] = prim->verts[v1];
      }
      flag = 1;
    }
    else if((prev != cur) && (cur != 0) && (prev != 0)){
      if(v1 != 0){
        v2 = v1-1;
        i--;
      }
      front_count++;
      front_list = (tools_GL2PSvertex*)tools_gl2psRealloc(front_list,
                                              sizeof(tools_GL2PSvertex)*front_count);
      tools_gl2psCutEdge(&prim->verts[v2], &prim->verts[v1],
                   plane, &front_list[front_count-1]);
      back_count++;
      back_list = (tools_GL2PSvertex*)tools_gl2psRealloc(back_list,
                                             sizeof(tools_GL2PSvertex)*back_count);
      back_list[back_count-1] = front_list[front_count-1];
      flag = 0;
    }
    prev = cur;
  }
  *front = tools_gl2psCreateSplitPrimitive2D(prim, front_count, front_list);
  *back = tools_gl2psCreateSplitPrimitive2D(prim, back_count, back_list);
  tools_gl2psFree(front_list);
  tools_gl2psFree(back_list);
}

inline tools_GLint tools_gl2psAddInBspImageTree(tools_GL2PScontext* gl2ps, tools_GL2PSprimitive *prim, tools_GL2PSbsptree2d **tree)
{
  tools_GLint ret = 0;
  tools_GL2PSprimitive *frontprim = NULL, *backprim = NULL;

  /* FIXME: until we consider the actual extent of text strings and
     pixmaps, never cull them. Otherwise the whole string/pixmap gets
     culled as soon as the reference point is hidden */
  if(prim->type == TOOLS_GL2PS_PIXMAP ||
     prim->type == TOOLS_GL2PS_TEXT ||
     prim->type == TOOLS_GL2PS_SPECIAL){
    return 1;
  }

  if(*tree == NULL){
    if((prim->type != TOOLS_GL2PS_IMAGEMAP) && (TOOLS_GL_FALSE == gl2ps->zerosurfacearea)){
      tools_gl2psAddPlanesInBspTreeImage(gl2ps->primitivetoadd, tree);
    }
    return 1;
  }
  else{
    switch(tools_gl2psCheckPrimitive(prim, (*tree)->plane)){
    case TOOLS_GL2PS_IN_BACK_OF: return tools_gl2psAddInBspImageTree(gl2ps, prim, &(*tree)->back);
    case TOOLS_GL2PS_IN_FRONT_OF:
      if((*tree)->front != NULL) return tools_gl2psAddInBspImageTree(gl2ps, prim, &(*tree)->front);
      else                       return 0;
    case TOOLS_GL2PS_SPANNING:
      tools_gl2psSplitPrimitive2D(prim, (*tree)->plane, &frontprim, &backprim);
      ret = tools_gl2psAddInBspImageTree(gl2ps, backprim, &(*tree)->back);
      if((*tree)->front != NULL){
        if(tools_gl2psAddInBspImageTree(gl2ps, frontprim, &(*tree)->front)){
          ret = 1;
        }
      }
      tools_gl2psFree(frontprim->verts);
      tools_gl2psFree(frontprim);
      tools_gl2psFree(backprim->verts);
      tools_gl2psFree(backprim);
      return ret;
    case TOOLS_GL2PS_COINCIDENT:
      if((*tree)->back != NULL){
        gl2ps->zerosurfacearea = TOOLS_GL_TRUE;
        ret = tools_gl2psAddInBspImageTree(gl2ps, prim, &(*tree)->back);
        gl2ps->zerosurfacearea = TOOLS_GL_FALSE;
        if(ret) return ret;
      }
      if((*tree)->front != NULL){
        gl2ps->zerosurfacearea = TOOLS_GL_TRUE;
        ret = tools_gl2psAddInBspImageTree(gl2ps, prim, &(*tree)->front);
        gl2ps->zerosurfacearea = TOOLS_GL_FALSE;
        if(ret) return ret;
      }
      if(prim->type == TOOLS_GL2PS_LINE) return 1;
      else                         return 0;
    }
  }
  return 0;
}

inline void tools_gl2psAddInImageTree(tools_GL2PScontext* gl2ps, void *data)
{
  tools_GL2PSprimitive *prim = *(tools_GL2PSprimitive **)data;
  gl2ps->primitivetoadd = prim;
  if(prim->type == TOOLS_GL2PS_IMAGEMAP && prim->data.image->format == TOOLS_GL2PS_IMAGEMAP_VISIBLE){
    prim->culled = 1;
  }
  else if(!tools_gl2psAddInBspImageTree(gl2ps, prim, &gl2ps->imagetree)){
    prim->culled = 1;
  }
  else if(prim->type == TOOLS_GL2PS_IMAGEMAP){
    prim->data.image->format = TOOLS_GL2PS_IMAGEMAP_VISIBLE;
  }
}

/* Boundary construction */

inline void tools_gl2psAddBoundaryInList(tools_GL2PSprimitive *prim, tools_GL2PSlist *list)
{
  tools_GL2PSprimitive *b;
  tools_GLshort i;
  tools_GL2PSxyz c;

  c[0] = c[1] = c[2] = 0.0F;
  for(i = 0; i < prim->numverts; i++){
    c[0] += prim->verts[i].xyz[0];
    c[1] += prim->verts[i].xyz[1];
  }
  c[0] /= prim->numverts;
  c[1] /= prim->numverts;

  for(i = 0; i < prim->numverts; i++){
    if(prim->boundary & (tools_GLint)pow(2., i)){
      b = (tools_GL2PSprimitive*)tools_gl2psMalloc(sizeof(tools_GL2PSprimitive));
      b->type = TOOLS_GL2PS_LINE;
      b->offset = prim->offset;
      b->ofactor = prim->ofactor;
      b->ounits = prim->ounits;
      b->pattern = prim->pattern;
      b->factor = prim->factor;
      b->culled = prim->culled;
      b->width = prim->width;
      b->linecap = prim->linecap;
      b->linejoin = prim->linejoin;
      b->boundary = 0;
      b->numverts = 2;
      b->verts = (tools_GL2PSvertex*)tools_gl2psMalloc(2 * sizeof(tools_GL2PSvertex));

#if 0 /* FIXME: need to work on boundary offset... */
      v[0] = c[0] - prim->verts[i].xyz[0];
      v[1] = c[1] - prim->verts[i].xyz[1];
      v[2] = 0.0F;
      norm = tools_gl2psNorm(v);
      v[0] /= norm;
      v[1] /= norm;
      b->verts[0].xyz[0] = prim->verts[i].xyz[0] +0.1*v[0];
      b->verts[0].xyz[1] = prim->verts[i].xyz[1] +0.1*v[1];
      b->verts[0].xyz[2] = prim->verts[i].xyz[2];
      v[0] = c[0] - prim->verts[tools_gl2psGetIndex(i, prim->numverts)].xyz[0];
      v[1] = c[1] - prim->verts[tools_gl2psGetIndex(i, prim->numverts)].xyz[1];
      norm = tools_gl2psNorm(v);
      v[0] /= norm;
      v[1] /= norm;
      b->verts[1].xyz[0] = prim->verts[tools_gl2psGetIndex(i, prim->numverts)].xyz[0] +0.1*v[0];
      b->verts[1].xyz[1] = prim->verts[tools_gl2psGetIndex(i, prim->numverts)].xyz[1] +0.1*v[1];
      b->verts[1].xyz[2] = prim->verts[tools_gl2psGetIndex(i, prim->numverts)].xyz[2];
#else
      b->verts[0].xyz[0] = prim->verts[i].xyz[0];
      b->verts[0].xyz[1] = prim->verts[i].xyz[1];
      b->verts[0].xyz[2] = prim->verts[i].xyz[2];
      b->verts[1].xyz[0] = prim->verts[tools_gl2psGetIndex(i, prim->numverts)].xyz[0];
      b->verts[1].xyz[1] = prim->verts[tools_gl2psGetIndex(i, prim->numverts)].xyz[1];
      b->verts[1].xyz[2] = prim->verts[tools_gl2psGetIndex(i, prim->numverts)].xyz[2];
#endif

      b->verts[0].rgba[0] = 0.0F;
      b->verts[0].rgba[1] = 0.0F;
      b->verts[0].rgba[2] = 0.0F;
      b->verts[0].rgba[3] = 0.0F;
      b->verts[1].rgba[0] = 0.0F;
      b->verts[1].rgba[1] = 0.0F;
      b->verts[1].rgba[2] = 0.0F;
      b->verts[1].rgba[3] = 0.0F;
      tools_gl2psListAdd(list, &b);
    }
  }

}

inline void tools_gl2psBuildPolygonBoundary(tools_GL2PSbsptree *tree)
{
  tools_GLint i;
  tools_GL2PSprimitive *prim;

  if(!tree) return;
  tools_gl2psBuildPolygonBoundary(tree->back);
  for(i = 0; i < tools_gl2psListNbr(tree->primitives); i++){
    prim = *(tools_GL2PSprimitive**)tools_gl2psListPointer(tree->primitives, i);
    if(prim->boundary) tools_gl2psAddBoundaryInList(prim, tree->primitives);
  }
  tools_gl2psBuildPolygonBoundary(tree->front);
}

/*********************************************************************
 *
 * Feedback buffer parser
 *
 *********************************************************************/

TOOLS_GL2PSDLL_API void tools_gl2psAddPolyPrimitive(tools_GL2PScontext* gl2ps, tools_GLshort type, tools_GLshort numverts,
                                        tools_GL2PSvertex *verts, tools_GLint offset,
                                        tools_GLfloat ofactor, tools_GLfloat ounits,
                                        tools_GLushort pattern, tools_GLint factor,
                                        tools_GLfloat width, tools_GLint linecap,
                                        tools_GLint linejoin,char boundary)
{
  tools_GL2PSprimitive *prim;

  prim = (tools_GL2PSprimitive*)tools_gl2psMalloc(sizeof(tools_GL2PSprimitive));
  prim->type = type;
  prim->numverts = numverts;
  prim->verts = (tools_GL2PSvertex*)tools_gl2psMalloc(numverts * sizeof(tools_GL2PSvertex));
  memcpy(prim->verts, verts, numverts * sizeof(tools_GL2PSvertex));
  prim->boundary = boundary;
  prim->offset = (char)offset;
  prim->ofactor = ofactor;
  prim->ounits = ounits;
  prim->pattern = pattern;
  prim->factor = factor;
  prim->width = width;
  prim->linecap = linecap;
  prim->linejoin = linejoin;
  prim->culled = 0;

  /* FIXME: here we should have an option to split stretched
     tris/quads to enhance SIMPLE_SORT */

  tools_gl2psListAdd(gl2ps->primitives, &prim);
}

inline tools_GLint tools_gl2psGetVertex(tools_GL2PScontext* gl2ps, tools_GL2PSvertex *v, tools_GLfloat *p)
{
  tools_GLint i;

  v->xyz[0] = p[0];
  v->xyz[1] = p[1];
  v->xyz[2] = p[2];

  if(gl2ps->colormode == TOOLS_GL_COLOR_INDEX && gl2ps->colorsize > 0){
    i = (tools_GLint)(p[3] + 0.5);
    v->rgba[0] = gl2ps->colormap[i][0];
    v->rgba[1] = gl2ps->colormap[i][1];
    v->rgba[2] = gl2ps->colormap[i][2];
    v->rgba[3] = gl2ps->colormap[i][3];
    return 4;
  }
  else{
    v->rgba[0] = p[3];
    v->rgba[1] = p[4];
    v->rgba[2] = p[5];
    v->rgba[3] = p[6];
    return 7;
  }
}

inline void tools_gl2psParseFeedbackBuffer(tools_GL2PScontext* gl2ps, tools_GLint used)
{
  char flag;
  tools_GLushort pattern = 0;
  tools_GLboolean boundary;
  tools_GLint i, sizeoffloat, count, v, vtot, offset = 0, factor = 0, auxindex = 0;
  tools_GLint lcap = 0, ljoin = 0;
  tools_GLfloat lwidth = 1.0F, psize = 1.0F, ofactor = 0.0F, ounits = 0.0F;
  tools_GLfloat *current;
  tools_GL2PSvertex vertices[3];
  tools_GL2PSprimitive *prim;
  tools_GL2PSimagemap *node;

  current = gl2ps->feedback;
  boundary = gl2ps->boundary = TOOLS_GL_FALSE;

  while(used > 0){

    if(TOOLS_GL_TRUE == boundary) gl2ps->boundary = TOOLS_GL_TRUE;

    switch((tools_GLint)*current){
    case TOOLS_GL_POINT_TOKEN :
      current ++;
      used --;
      i = tools_gl2psGetVertex(gl2ps, &vertices[0], current);
      current += i;
      used    -= i;
      tools_gl2psAddPolyPrimitive(gl2ps, TOOLS_GL2PS_POINT, 1, vertices, 0, 0.0, 0.0,
                            pattern, factor, psize, lcap, ljoin, 0);
      break;
    case TOOLS_GL_LINE_TOKEN :
    case TOOLS_GL_LINE_RESET_TOKEN :
      current ++;
      used --;
      i = tools_gl2psGetVertex(gl2ps, &vertices[0], current);
      current += i;
      used    -= i;
      i = tools_gl2psGetVertex(gl2ps, &vertices[1], current);
      current += i;
      used    -= i;
      tools_gl2psAddPolyPrimitive(gl2ps, TOOLS_GL2PS_LINE, 2, vertices, 0, 0.0, 0.0,
                            pattern, factor, lwidth, lcap, ljoin, 0);
      break;
    case TOOLS_GL_POLYGON_TOKEN :
      count = (tools_GLint)current[1];
      current += 2;
      used -= 2;
      v = vtot = 0;
      while(count > 0 && used > 0){
        i = tools_gl2psGetVertex(gl2ps, &vertices[v], current);
        tools_gl2psAdaptVertexForBlending(gl2ps, &vertices[v]);
        current += i;
        used    -= i;
        count --;
        vtot++;
        if(v == 2){
          if(TOOLS_GL_TRUE == boundary){
            if(!count && vtot == 2) flag = 1|2|4;
            else if(!count) flag = 2|4;
            else if(vtot == 2) flag = 1|2;
            else flag = 2;
          }
          else
            flag = 0;
          tools_gl2psAddPolyPrimitive(gl2ps, TOOLS_GL2PS_TRIANGLE, 3, vertices, offset, ofactor,
                                ounits, pattern, factor, 1, lcap, ljoin,
                                flag);
          vertices[1] = vertices[2];
        }
        else
          v ++;
      }
      break;
    case TOOLS_GL_BITMAP_TOKEN :
    case TOOLS_GL_DRAW_PIXEL_TOKEN :
    case TOOLS_GL_COPY_PIXEL_TOKEN :
      current ++;
      used --;
      i = tools_gl2psGetVertex(gl2ps, &vertices[0], current);
      current += i;
      used    -= i;
      break;
    case TOOLS_GL_PASS_THROUGH_TOKEN :
      switch((tools_GLint)current[1]){
      case TOOLS_GL2PS_BEGIN_OFFSET_TOKEN :
        offset = 1;
        current += 2;
        used -= 2;
        ofactor = current[1];
        current += 2;
        used -= 2;
        ounits = current[1];
        break;
      case TOOLS_GL2PS_END_OFFSET_TOKEN :
        offset = 0;
        ofactor = 0.0;
        ounits = 0.0;
        break;
      case TOOLS_GL2PS_BEGIN_BOUNDARY_TOKEN : boundary = TOOLS_GL_TRUE; break;
      case TOOLS_GL2PS_END_BOUNDARY_TOKEN : boundary = TOOLS_GL_FALSE; break;
      case TOOLS_GL2PS_END_STIPPLE_TOKEN : pattern = 0; factor = 0; break;
      case TOOLS_GL2PS_BEGIN_BLEND_TOKEN : gl2ps->blending = TOOLS_GL_TRUE; break;
      case TOOLS_GL2PS_END_BLEND_TOKEN : gl2ps->blending = TOOLS_GL_FALSE; break;
      case TOOLS_GL2PS_BEGIN_STIPPLE_TOKEN :
        current += 2;
        used -= 2;
        pattern = (tools_GLushort)current[1];
        current += 2;
        used -= 2;
        factor = (tools_GLint)current[1];
        break;
      case TOOLS_GL2PS_SRC_BLEND_TOKEN :
        current += 2;
        used -= 2;
        gl2ps->blendfunc[0] = (tools_GLint)current[1];
        break;
      case TOOLS_GL2PS_DST_BLEND_TOKEN :
        current += 2;
        used -= 2;
        gl2ps->blendfunc[1] = (tools_GLint)current[1];
        break;
      case TOOLS_GL2PS_POINT_SIZE_TOKEN :
        current += 2;
        used -= 2;
        psize = current[1];
        break;
      case TOOLS_GL2PS_LINE_CAP_TOKEN :
        current += 2;
        used -= 2;
        lcap = (tools_GLint)current[1];
        break;
      case TOOLS_GL2PS_LINE_JOIN_TOKEN :
        current += 2;
        used -= 2;
        ljoin = (tools_GLint)current[1];
        break;
      case TOOLS_GL2PS_LINE_WIDTH_TOKEN :
        current += 2;
        used -= 2;
        lwidth = current[1];
        break;
      case TOOLS_GL2PS_IMAGEMAP_TOKEN :
        prim = (tools_GL2PSprimitive *)tools_gl2psMalloc(sizeof(tools_GL2PSprimitive));
        prim->type = TOOLS_GL2PS_IMAGEMAP;
        prim->boundary = 0;
        prim->numverts = 4;
        prim->verts = (tools_GL2PSvertex *)tools_gl2psMalloc(4 * sizeof(tools_GL2PSvertex));
        prim->culled = 0;
        prim->offset = 0;
        prim->ofactor = 0.0;
        prim->ounits = 0.0;
        prim->pattern = 0;
        prim->factor = 0;
        prim->width = 1;

        node = (tools_GL2PSimagemap*)tools_gl2psMalloc(sizeof(tools_GL2PSimagemap));
        node->image = (tools_GL2PSimage*)tools_gl2psMalloc(sizeof(tools_GL2PSimage));
        node->image->type = 0;
        node->image->format = 0;
        node->image->zoom_x = 1.0F;
        node->image->zoom_y = 1.0F;
        node->next = NULL;

        if(gl2ps->imagemap_head == NULL)
          gl2ps->imagemap_head = node;
        else
          gl2ps->imagemap_tail->next = node;
        gl2ps->imagemap_tail = node;
        prim->data.image = node->image;

        current += 2; used -= 2;
        i = tools_gl2psGetVertex(gl2ps, &prim->verts[0], &current[1]);
        current += i; used -= i;

        node->image->width = (tools_GLint)current[2];
        current += 2; used -= 2;
        node->image->height = (tools_GLint)current[2];
        prim->verts[0].xyz[0] = prim->verts[0].xyz[0] - (int)(node->image->width / 2) + 0.5F;
        prim->verts[0].xyz[1] = prim->verts[0].xyz[1] - (int)(node->image->height / 2) + 0.5F;
        for(i = 1; i < 4; i++){
          for(v = 0; v < 3; v++){
            prim->verts[i].xyz[v] = prim->verts[0].xyz[v];
            prim->verts[i].rgba[v] = prim->verts[0].rgba[v];
          }
          prim->verts[i].rgba[v] = prim->verts[0].rgba[v];
        }
        prim->verts[1].xyz[0] = prim->verts[1].xyz[0] + node->image->width;
        prim->verts[2].xyz[0] = prim->verts[1].xyz[0];
        prim->verts[2].xyz[1] = prim->verts[2].xyz[1] + node->image->height;
        prim->verts[3].xyz[1] = prim->verts[2].xyz[1];

        sizeoffloat = sizeof(tools_GLfloat);
        v = 2 * sizeoffloat;
        vtot = node->image->height + node->image->height *
          ((node->image->width - 1) / 8);
        node->image->pixels = (tools_GLfloat*)tools_gl2psMalloc(v + vtot);
        node->image->pixels[0] = prim->verts[0].xyz[0];
        node->image->pixels[1] = prim->verts[0].xyz[1];

        for(i = 0; i < vtot; i += sizeoffloat){
          current += 2; used -= 2;
          if((vtot - i) >= 4)
            memcpy(&(((char*)(node->image->pixels))[i + v]), &(current[2]), sizeoffloat);
          else
            memcpy(&(((char*)(node->image->pixels))[i + v]), &(current[2]), vtot - i);
        }
        current++; used--;
        tools_gl2psListAdd(gl2ps->primitives, &prim);
        break;
      case TOOLS_GL2PS_DRAW_PIXELS_TOKEN :
      case TOOLS_GL2PS_TEXT_TOKEN :
        if(auxindex < tools_gl2psListNbr(gl2ps->auxprimitives))
          tools_gl2psListAdd(gl2ps->primitives,
                       tools_gl2psListPointer(gl2ps->auxprimitives, auxindex++));
        else
          tools_gl2psMsg(TOOLS_GL2PS_ERROR, "Wrong number of auxiliary tokens in buffer");
        break;
      }
      current += 2;
      used -= 2;
      break;
    default :
      tools_gl2psMsg(TOOLS_GL2PS_WARNING, "Unknown token in buffer");
      current ++;
      used --;
      break;
    }
  }

  tools_gl2psListReset(gl2ps->auxprimitives);
}

/*********************************************************************
 *
 * PostScript routines
 *
 *********************************************************************/

inline void tools_gl2psWriteByte(tools_GL2PScontext* gl2ps, unsigned char byte)
{
  unsigned char h = byte / 16;
  unsigned char l = byte % 16;
  tools_gl2psPrintf(gl2ps,"%x%x", h, l);
}

inline void tools_gl2psPrintPostScriptPixmap(tools_GL2PScontext* gl2ps, tools_GLfloat x, tools_GLfloat y, tools_GL2PSimage *im,int greyscale,int nbit)
{
  tools_GLuint nbhex, nbyte, nrgb, nbits;
  tools_GLuint row, col, ibyte, icase;
  tools_GLfloat dr = 0., dg = 0., db = 0., fgrey;
  unsigned char red = 0, green = 0, blue = 0, b, grey;
  tools_GLuint width = (tools_GLuint)im->width;
  tools_GLuint height = (tools_GLuint)im->height;

  /* FIXME: should we define an option for these? Or just keep the
     8-bit per component case? */
/*G.Barrand: have the two below lines in arguments to quiet Coverity about dead code.
  int greyscale = 0; // set to 1 to output greyscale image.
  int nbit = 8; // number of bits per color compoment (2, 4 or 8).
*/

  if((width <= 0) || (height <= 0)) return;

  tools_gl2psPrintf(gl2ps,"gsave\n");
  tools_gl2psPrintf(gl2ps,"%.2f %.2f translate\n", x, y);
  tools_gl2psPrintf(gl2ps,"%.2f %.2f scale\n", width * im->zoom_x, height * im->zoom_y);

  if(greyscale){ /* greyscale */
    tools_gl2psPrintf(gl2ps,"/picstr %d string def\n", width);
    tools_gl2psPrintf(gl2ps,"%d %d %d\n", width, height, 8);
    tools_gl2psPrintf(gl2ps,"[ %d 0 0 -%d 0 %d ]\n", width, height, height);
    tools_gl2psPrintf(gl2ps,"{ currentfile picstr readhexstring pop }\n");
    tools_gl2psPrintf(gl2ps,"image\n");
    for(row = 0; row < height; row++){
      for(col = 0; col < width; col++){
        tools_gl2psGetRGB(im, col, row, &dr, &dg, &db);
        fgrey = (0.30F * dr + 0.59F * dg + 0.11F * db);
        grey = (unsigned char)(255. * fgrey);
        tools_gl2psWriteByte(gl2ps, grey);
      }
      tools_gl2psPrintf(gl2ps,"\n");
    }
    nbhex = width * height * 2;
    tools_gl2psPrintf(gl2ps,"%%%% nbhex digit          :%d\n", nbhex);
  }
  else if(nbit == 2){ /* color, 2 bits for r and g and b; rgbs following each other */
    nrgb = width  * 3;
    nbits = nrgb * nbit;
    nbyte = nbits / 8;
    if((nbyte * 8) != nbits) nbyte++;
    tools_gl2psPrintf(gl2ps,"/rgbstr %d string def\n", nbyte);
    tools_gl2psPrintf(gl2ps,"%d %d %d\n", width, height, nbit);
    tools_gl2psPrintf(gl2ps,"[ %d 0 0 -%d 0 %d ]\n", width, height, height);
    tools_gl2psPrintf(gl2ps,"{ currentfile rgbstr readhexstring pop }\n");
    tools_gl2psPrintf(gl2ps,"false 3\n");
    tools_gl2psPrintf(gl2ps,"colorimage\n");
    for(row = 0; row < height; row++){
      icase = 1;
      col = 0;
      b = 0;
      for(ibyte = 0; ibyte < nbyte; ibyte++){
        if(icase == 1) {
          if(col < width) {
            tools_gl2psGetRGB(im, col, row, &dr, &dg, &db);
          }
          else {
            dr = dg = db = 0;
          }
          col++;
          red = (unsigned char)(3. * dr);
          green = (unsigned char)(3. * dg);
          blue = (unsigned char)(3. * db);
          b = red;
          b = (b<<2) + green;
          b = (b<<2) + blue;
          if(col < width) {
            tools_gl2psGetRGB(im, col, row, &dr, &dg, &db);
          }
          else {
            dr = dg = db = 0;
          }
          col++;
          red = (unsigned char)(3. * dr);
          green = (unsigned char)(3. * dg);
          blue = (unsigned char)(3. * db);
          b = (b<<2) + red;
          tools_gl2psWriteByte(gl2ps, b);
          b = 0;
          icase++;
        }
        else if(icase == 2) {
          b = green;
          b = (b<<2) + blue;
          if(col < width) {
            tools_gl2psGetRGB(im, col, row, &dr, &dg, &db);
          }
          else {
            dr = dg = db = 0;
          }
          col++;
          red = (unsigned char)(3. * dr);
          green = (unsigned char)(3. * dg);
          blue = (unsigned char)(3. * db);
          b = (b<<2) + red;
          b = (b<<2) + green;
          tools_gl2psWriteByte(gl2ps, b);
          b = 0;
          icase++;
        }
        else if(icase == 3) {
          b = blue;
          if(col < width) {
            tools_gl2psGetRGB(im, col, row, &dr, &dg, &db);
          }
          else {
            dr = dg = db = 0;
          }
          col++;
          red = (unsigned char)(3. * dr);
          green = (unsigned char)(3. * dg);
          blue = (unsigned char)(3. * db);
          b = (b<<2) + red;
          b = (b<<2) + green;
          b = (b<<2) + blue;
          tools_gl2psWriteByte(gl2ps, b);
          b = 0;
          icase = 1;
        }
      }
      tools_gl2psPrintf(gl2ps,"\n");
    }
  }
  else if(nbit == 4){ /* color, 4 bits for r and g and b; rgbs following each other */
    nrgb = width  * 3;
    nbits = nrgb * nbit;
    nbyte = nbits / 8;
    if((nbyte * 8) != nbits) nbyte++;
    tools_gl2psPrintf(gl2ps,"/rgbstr %d string def\n", nbyte);
    tools_gl2psPrintf(gl2ps,"%d %d %d\n", width, height, nbit);
    tools_gl2psPrintf(gl2ps,"[ %d 0 0 -%d 0 %d ]\n", width, height, height);
    tools_gl2psPrintf(gl2ps,"{ currentfile rgbstr readhexstring pop }\n");
    tools_gl2psPrintf(gl2ps,"false 3\n");
    tools_gl2psPrintf(gl2ps,"colorimage\n");
    for(row = 0; row < height; row++){
      col = 0;
      icase = 1;
      for(ibyte = 0; ibyte < nbyte; ibyte++){
        if(icase == 1) {
          if(col < width) {
            tools_gl2psGetRGB(im, col, row, &dr, &dg, &db);
          }
          else {
            dr = dg = db = 0;
          }
          col++;
          red = (unsigned char)(15. * dr);
          green = (unsigned char)(15. * dg);
          tools_gl2psPrintf(gl2ps,"%x%x", red, green);
          icase++;
        }
        else if(icase == 2) {
          blue = (unsigned char)(15. * db);
          if(col < width) {
            tools_gl2psGetRGB(im, col, row, &dr, &dg, &db);
          }
          else {
            dr = dg = db = 0;
          }
          col++;
          red = (unsigned char)(15. * dr);
          tools_gl2psPrintf(gl2ps,"%x%x", blue, red);
          icase++;
        }
        else if(icase == 3) {
          green = (unsigned char)(15. * dg);
          blue = (unsigned char)(15. * db);
          tools_gl2psPrintf(gl2ps,"%x%x", green, blue);
          icase = 1;
        }
      }
      tools_gl2psPrintf(gl2ps,"\n");
    }
  }
  else{ /* 8 bit for r and g and b */
    nbyte = width * 3;
    tools_gl2psPrintf(gl2ps,"/rgbstr %d string def\n", nbyte);
    tools_gl2psPrintf(gl2ps,"%d %d %d\n", width, height, 8);
    tools_gl2psPrintf(gl2ps,"[ %d 0 0 -%d 0 %d ]\n", width, height, height);
    tools_gl2psPrintf(gl2ps,"{ currentfile rgbstr readhexstring pop }\n");
    tools_gl2psPrintf(gl2ps,"false 3\n");
    tools_gl2psPrintf(gl2ps,"colorimage\n");
    for(row = 0; row < height; row++){
      for(col = 0; col < width; col++){
        tools_gl2psGetRGB(im, col, row, &dr, &dg, &db);
        red = (unsigned char)(255. * dr);
        tools_gl2psWriteByte(gl2ps, red);
        green = (unsigned char)(255. * dg);
        tools_gl2psWriteByte(gl2ps, green);
        blue = (unsigned char)(255. * db);
        tools_gl2psWriteByte(gl2ps, blue);
      }
      tools_gl2psPrintf(gl2ps,"\n");
    }
  }

  tools_gl2psPrintf(gl2ps,"grestore\n");
}

inline void tools_gl2psPrintPostScriptImagemap(tools_GL2PScontext* gl2ps, tools_GLfloat x, tools_GLfloat y,
                                         tools_GLsizei width, tools_GLsizei height,
                                         const unsigned char *imagemap){
  int i, size;

  if((width <= 0) || (height <= 0)) return;

  size = height + height * (width - 1) / 8;

  tools_gl2psPrintf(gl2ps,"gsave\n");
  tools_gl2psPrintf(gl2ps,"%.2f %.2f translate\n", x, y);
  tools_gl2psPrintf(gl2ps,"%d %d scale\n%d %d\ntrue\n", width, height,width, height);
  tools_gl2psPrintf(gl2ps,"[ %d 0 0 -%d 0 %d ] {<", width, height, height); /*G.Barrand : add last height.*/
  for(i = 0; i < size; i++){
    tools_gl2psWriteByte(gl2ps, *imagemap);
    imagemap++;
  }
  tools_gl2psPrintf(gl2ps,">} imagemask\ngrestore\n");
}

inline void tools_gl2psPrintPostScriptHeader(tools_GL2PScontext* gl2ps)
{
  time_t now;

  /* Since compression is not part of the PostScript standard,
     compressed PostScript files are just gzipped PostScript files
     ("ps.gz" or "eps.gz") */
  tools_gl2psPrintGzipHeader(gl2ps);

  time(&now);

  if(gl2ps->format == TOOLS_GL2PS_PS){
    tools_gl2psPrintf(gl2ps,"%%!PS-Adobe-3.0\n");
  }
  else{
    tools_gl2psPrintf(gl2ps,"%%!PS-Adobe-3.0 EPSF-3.0\n");
  }

  tools_gl2psPrintf(gl2ps,"%%%%Title: %s\n"
              "%%%%Creator: GL2PS %d.%d.%d%s, %s\n"
              "%%%%For: %s\n"
              "%%%%CreationDate: %s"
              "%%%%LanguageLevel: 3\n"
              "%%%%DocumentData: Clean7Bit\n"
              "%%%%Pages: 1\n",
              gl2ps->title, TOOLS_GL2PS_MAJOR_VERSION, TOOLS_GL2PS_MINOR_VERSION,
              TOOLS_GL2PS_PATCH_VERSION, TOOLS_GL2PS_EXTRA_VERSION, TOOLS_GL2PS_COPYRIGHT,
              gl2ps->producer, ctime(&now));

  if(gl2ps->format == TOOLS_GL2PS_PS){
    tools_gl2psPrintf(gl2ps,"%%%%Orientation: %s\n"
                "%%%%DocumentMedia: Default %d %d 0 () ()\n",
                (gl2ps->options & TOOLS_GL2PS_LANDSCAPE) ? "Landscape" : "Portrait",
                (gl2ps->options & TOOLS_GL2PS_LANDSCAPE) ? (int)gl2ps->viewport[3] :
                (int)gl2ps->viewport[2],
                (gl2ps->options & TOOLS_GL2PS_LANDSCAPE) ? (int)gl2ps->viewport[2] :
                (int)gl2ps->viewport[3]);
  }

  tools_gl2psPrintf(gl2ps,"%%%%BoundingBox: %d %d %d %d\n"
              "%%%%EndComments\n",
              (gl2ps->options & TOOLS_GL2PS_LANDSCAPE) ? (int)gl2ps->viewport[1] :
              (int)gl2ps->viewport[0],
              (gl2ps->options & TOOLS_GL2PS_LANDSCAPE) ? (int)gl2ps->viewport[0] :
              (int)gl2ps->viewport[1],
              (gl2ps->options & TOOLS_GL2PS_LANDSCAPE) ? (int)gl2ps->viewport[3] :
              (int)gl2ps->viewport[2],
              (gl2ps->options & TOOLS_GL2PS_LANDSCAPE) ? (int)gl2ps->viewport[2] :
              (int)gl2ps->viewport[3]);

  /* RGB color: r g b C (replace C by G in output to change from rgb to gray)
     Grayscale: r g b G
     Font choose: size fontname FC
     Text string: (string) x y size fontname S??
     Rotated text string: (string) angle x y size fontname S??R
     Point primitive: x y size P
     Line width: width W
     Line start: x y LS
     Line joining last point: x y L
     Line end: x y LE
     Flat-shaded triangle: x3 y3 x2 y2 x1 y1 T
     Smooth-shaded triangle: x3 y3 r3 g3 b3 x2 y2 r2 g2 b2 x1 y1 r1 g1 b1 ST */

  tools_gl2psPrintf(gl2ps,"%%%%BeginProlog\n"
              "/gl2psdict 64 dict def gl2psdict begin\n"
              "/tryPS3shading %s def %% set to false to force subdivision\n"
              "/rThreshold %g def %% red component subdivision threshold\n"
              "/gThreshold %g def %% green component subdivision threshold\n"
              "/bThreshold %g def %% blue component subdivision threshold\n",
              (gl2ps->options & TOOLS_GL2PS_NO_PS3_SHADING) ? "false" : "true",
              gl2ps->threshold[0], gl2ps->threshold[1], gl2ps->threshold[2]);

  tools_gl2psPrintf(gl2ps,"/BD { bind def } bind def\n"
              "/C  { setrgbcolor } BD\n"
              "/G  { 0.082 mul exch 0.6094 mul add exch 0.3086 mul add neg 1.0 add setgray } BD\n"
              "/W  { setlinewidth } BD\n"
              "/LC  { setlinecap } BD\n"
              "/LJ  { setlinejoin } BD\n");

  tools_gl2psPrintf(gl2ps,"/FC { findfont exch /SH exch def SH scalefont setfont } BD\n"
              "/SW { dup stringwidth pop } BD\n"
              "/S  { FC moveto show } BD\n"
              "/SBC{ FC moveto SW -2 div 0 rmoveto show } BD\n"
              "/SBR{ FC moveto SW neg 0 rmoveto show } BD\n"
              "/SCL{ FC moveto 0 SH -2 div rmoveto show } BD\n"
              "/SCC{ FC moveto SW -2 div SH -2 div rmoveto show } BD\n"
              "/SCR{ FC moveto SW neg SH -2 div rmoveto show } BD\n"
              "/STL{ FC moveto 0 SH neg rmoveto show } BD\n"
              "/STC{ FC moveto SW -2 div SH neg rmoveto show } BD\n"
              "/STR{ FC moveto SW neg SH neg rmoveto show } BD\n");

  /* rotated text routines: same nameanem with R appended */

  tools_gl2psPrintf(gl2ps,"/FCT { FC translate 0 0 } BD\n"
              "/SR  { gsave FCT moveto rotate show grestore } BD\n"
              "/SBCR{ gsave FCT moveto rotate SW -2 div 0 rmoveto show grestore } BD\n"
              "/SBRR{ gsave FCT moveto rotate SW neg 0 rmoveto show grestore } BD\n"
              "/SCLR{ gsave FCT moveto rotate 0 SH -2 div rmoveto show grestore} BD\n");
  tools_gl2psPrintf(gl2ps,"/SCCR{ gsave FCT moveto rotate SW -2 div SH -2 div rmoveto show grestore} BD\n"
              "/SCRR{ gsave FCT moveto rotate SW neg SH -2 div rmoveto show grestore} BD\n"
              "/STLR{ gsave FCT moveto rotate 0 SH neg rmoveto show grestore } BD\n"
              "/STCR{ gsave FCT moveto rotate SW -2 div SH neg rmoveto show grestore } BD\n"
              "/STRR{ gsave FCT moveto rotate SW neg SH neg rmoveto show grestore } BD\n");

  tools_gl2psPrintf(gl2ps,"/P  { newpath 0.0 360.0 arc closepath fill } BD\n"
              "/LS { newpath moveto } BD\n"
              "/L  { lineto } BD\n"
              "/LE { lineto stroke } BD\n"
              "/T  { newpath moveto lineto lineto closepath fill } BD\n");

  /* Smooth-shaded triangle with PostScript level 3 shfill operator:
        x3 y3 r3 g3 b3 x2 y2 r2 g2 b2 x1 y1 r1 g1 b1 STshfill */

  tools_gl2psPrintf(gl2ps,"/STshfill {\n"
              "      /b1 exch def /g1 exch def /r1 exch def /y1 exch def /x1 exch def\n"
              "      /b2 exch def /g2 exch def /r2 exch def /y2 exch def /x2 exch def\n"
              "      /b3 exch def /g3 exch def /r3 exch def /y3 exch def /x3 exch def\n"
              "      gsave << /ShadingType 4 /ColorSpace [/DeviceRGB]\n"
              "      /DataSource [ 0 x1 y1 r1 g1 b1 0 x2 y2 r2 g2 b2 0 x3 y3 r3 g3 b3 ] >>\n"
              "      shfill grestore } BD\n");

  /* Flat-shaded triangle with middle color:
        x3 y3 r3 g3 b3 x2 y2 r2 g2 b2 x1 y1 r1 g1 b1 Tm */

  tools_gl2psPrintf(gl2ps,/* stack : x3 y3 r3 g3 b3 x2 y2 r2 g2 b2 x1 y1 r1 g1 b1 */
              "/Tm { 3 -1 roll 8 -1 roll 13 -1 roll add add 3 div\n" /* r = (r1+r2+r3)/3 */
              /* stack : x3 y3 g3 b3 x2 y2 g2 b2 x1 y1 g1 b1 r */
              "      3 -1 roll 7 -1 roll 11 -1 roll add add 3 div\n" /* g = (g1+g2+g3)/3 */
              /* stack : x3 y3 b3 x2 y2 b2 x1 y1 b1 r g b */
              "      3 -1 roll 6 -1 roll 9 -1 roll add add 3 div" /* b = (b1+b2+b3)/3 */
              /* stack : x3 y3 x2 y2 x1 y1 r g b */
              " C T } BD\n");

  /* Split triangle in four sub-triangles (at sides middle points) and call the
     STnoshfill procedure on each, interpolating the colors in RGB space:
        x3 y3 r3 g3 b3 x2 y2 r2 g2 b2 x1 y1 r1 g1 b1 STsplit
     (in procedure comments key: (Vi) = xi yi ri gi bi) */

  tools_gl2psPrintf(gl2ps,"/STsplit {\n"
              "      4 index 15 index add 0.5 mul\n" /* x13 = (x1+x3)/2 */
              "      4 index 15 index add 0.5 mul\n" /* y13 = (y1+y3)/2 */
              "      4 index 15 index add 0.5 mul\n" /* r13 = (r1+r3)/2 */
              "      4 index 15 index add 0.5 mul\n" /* g13 = (g1+g3)/2 */
              "      4 index 15 index add 0.5 mul\n" /* b13 = (b1+b3)/2 */
              "      5 copy 5 copy 25 15 roll\n");

  /* at his point, stack = (V3) (V13) (V13) (V13) (V2) (V1) */

  tools_gl2psPrintf(gl2ps,"      9 index 30 index add 0.5 mul\n" /* x23 = (x2+x3)/2 */
              "      9 index 30 index add 0.5 mul\n" /* y23 = (y2+y3)/2 */
              "      9 index 30 index add 0.5 mul\n" /* r23 = (r2+r3)/2 */
              "      9 index 30 index add 0.5 mul\n" /* g23 = (g2+g3)/2 */
              "      9 index 30 index add 0.5 mul\n" /* b23 = (b2+b3)/2 */
              "      5 copy 5 copy 35 5 roll 25 5 roll 15 5 roll\n");

  /* stack = (V3) (V13) (V23) (V13) (V23) (V13) (V23) (V2) (V1) */

  tools_gl2psPrintf(gl2ps,"      4 index 10 index add 0.5 mul\n" /* x12 = (x1+x2)/2 */
              "      4 index 10 index add 0.5 mul\n" /* y12 = (y1+y2)/2 */
              "      4 index 10 index add 0.5 mul\n" /* r12 = (r1+r2)/2 */
              "      4 index 10 index add 0.5 mul\n" /* g12 = (g1+g2)/2 */
              "      4 index 10 index add 0.5 mul\n" /* b12 = (b1+b2)/2 */
              "      5 copy 5 copy 40 5 roll 25 5 roll 15 5 roll 25 5 roll\n");

  /* stack = (V3) (V13) (V23) (V13) (V12) (V23) (V13) (V1) (V12) (V23) (V12) (V2) */

  tools_gl2psPrintf(gl2ps,"      STnoshfill STnoshfill STnoshfill STnoshfill } BD\n");

  /* Gouraud shaded triangle using recursive subdivision until the difference
     between corner colors does not exceed the thresholds:
        x3 y3 r3 g3 b3 x2 y2 r2 g2 b2 x1 y1 r1 g1 b1 STnoshfill  */

  tools_gl2psPrintf(gl2ps,"/STnoshfill {\n"
              "      2 index 8 index sub abs rThreshold gt\n" /* |r1-r2|>rth */
              "      { STsplit }\n"
              "      { 1 index 7 index sub abs gThreshold gt\n" /* |g1-g2|>gth */
              "        { STsplit }\n"
              "        { dup 6 index sub abs bThreshold gt\n" /* |b1-b2|>bth */
              "          { STsplit }\n"
              "          { 2 index 13 index sub abs rThreshold gt\n" /* |r1-r3|>rht */
              "            { STsplit }\n"
              "            { 1 index 12 index sub abs gThreshold gt\n" /* |g1-g3|>gth */
              "              { STsplit }\n"
              "              { dup 11 index sub abs bThreshold gt\n" /* |b1-b3|>bth */
              "                { STsplit }\n"
              "                { 7 index 13 index sub abs rThreshold gt\n"); /* |r2-r3|>rht */
  tools_gl2psPrintf(gl2ps,"                  { STsplit }\n"
              "                  { 6 index 12 index sub abs gThreshold gt\n" /* |g2-g3|>gth */
              "                    { STsplit }\n"
              "                    { 5 index 11 index sub abs bThreshold gt\n" /* |b2-b3|>bth */
              "                      { STsplit }\n"
              "                      { Tm }\n" /* all colors sufficiently similar */
              "                      ifelse }\n"
              "                    ifelse }\n"
              "                  ifelse }\n"
              "                ifelse }\n"
              "              ifelse }\n"
              "            ifelse }\n"
              "          ifelse }\n"
              "        ifelse }\n"
              "      ifelse } BD\n");

  tools_gl2psPrintf(gl2ps,"tryPS3shading\n"
              "{ /shfill where\n"
              "  { /ST { STshfill } BD }\n"
              "  { /ST { STnoshfill } BD }\n"
              "  ifelse }\n"
              "{ /ST { STnoshfill } BD }\n"
              "ifelse\n");

  tools_gl2psPrintf(gl2ps,"end\n"
              "%%%%EndProlog\n"
              "%%%%BeginSetup\n"
              "/DeviceRGB setcolorspace\n"
              "gl2psdict begin\n"
              "%%%%EndSetup\n"
              "%%%%Page: 1 1\n"
              "%%%%BeginPageSetup\n");

  if(gl2ps->options & TOOLS_GL2PS_LANDSCAPE){
    tools_gl2psPrintf(gl2ps,"%d 0 translate 90 rotate\n",
                (int)gl2ps->viewport[3]);
  }

  tools_gl2psPrintf(gl2ps,"%%%%EndPageSetup\n"
              "mark\n"
              "gsave\n"
              "1.0 1.0 scale\n");

  if(gl2ps->options & TOOLS_GL2PS_DRAW_BACKGROUND){
    tools_gl2psPrintf(gl2ps,"%g %g %g C\n"
                "newpath %d %d moveto %d %d lineto %d %d lineto %d %d lineto\n"
                "closepath fill\n",
                gl2ps->bgcolor[0], gl2ps->bgcolor[1], gl2ps->bgcolor[2],
                (int)gl2ps->viewport[0], (int)gl2ps->viewport[1], (int)gl2ps->viewport[2],
                (int)gl2ps->viewport[1], (int)gl2ps->viewport[2], (int)gl2ps->viewport[3],
                (int)gl2ps->viewport[0], (int)gl2ps->viewport[3]);
  }
}

inline void tools_gl2psPrintPostScriptColor(tools_GL2PScontext* gl2ps, tools_GL2PSrgba rgba)
{
  if(!tools_gl2psSameColor(gl2ps->lastrgba, rgba)){
    tools_gl2psSetLastColor(gl2ps, rgba);
    tools_gl2psPrintf(gl2ps,"%g %g %g C\n", rgba[0], rgba[1], rgba[2]);
  }
}

inline void tools_gl2psResetPostScriptColor(tools_GL2PScontext* gl2ps)
{
  gl2ps->lastrgba[0] = gl2ps->lastrgba[1] = gl2ps->lastrgba[2] = -1.;
}

inline void tools_gl2psEndPostScriptLine(tools_GL2PScontext* gl2ps)
{
  int i;
  if(gl2ps->lastvertex.rgba[0] >= 0.){
    tools_gl2psPrintf(gl2ps,"%g %g LE\n", gl2ps->lastvertex.xyz[0], gl2ps->lastvertex.xyz[1]);
    for(i = 0; i < 3; i++)
      gl2ps->lastvertex.xyz[i] = -1.;
    for(i = 0; i < 4; i++)
      gl2ps->lastvertex.rgba[i] = -1.;
  }
}

inline void tools_gl2psParseStipplePattern(tools_GLushort pattern, tools_GLint factor,
                                     int *nb, int array[10])
{
  int i, n;
  int on[8] = {0, 0, 0, 0, 0, 0, 0, 0};
  int off[8] = {0, 0, 0, 0, 0, 0, 0, 0};
  char tmp[16];

  /* extract the 16 bits from the OpenGL stipple pattern */
  for(n = 15; n >= 0; n--){
    tmp[n] = (char)(pattern & 0x01);
    pattern >>= 1;
  }
  /* compute the on/off pixel sequence */
  n = 0;
  for(i = 0; i < 8; i++){
    while(n < 16 && !tmp[n]){ off[i]++; n++; }
    while(n < 16 && tmp[n]){ on[i]++; n++; }
    if(n >= 15){ i++; break; }
  }

  /* store the on/off array from right to left, starting with off
     pixels. The PostScript specification allows for at most 11
     elements in the on/off array, so we limit ourselves to 5 on/off
     couples (our longest possible array is thus [on4 off4 on3 off3
     on2 off2 on1 off1 on0 off0]) */
  *nb = 0;
  for(n = i - 1; n >= 0; n--){
    array[(*nb)++] = factor * on[n];
    array[(*nb)++] = factor * off[n];
    if(*nb == 10) break;
  }
}

inline int tools_gl2psPrintPostScriptDash(tools_GL2PScontext* gl2ps, tools_GLushort pattern, tools_GLint factor, const char *str)
{
  int len = 0, i, n, array[10];

  if(pattern == gl2ps->lastpattern && factor == gl2ps->lastfactor)
    return 0;

  gl2ps->lastpattern = pattern;
  gl2ps->lastfactor = factor;

  if(!pattern || !factor){
    /* solid line */
    len += tools_gl2psPrintf(gl2ps,"[] 0 %s\n", str);
  }
  else{
    tools_gl2psParseStipplePattern(pattern, factor, &n, array);
    len += tools_gl2psPrintf(gl2ps,"[");
    for(i = 0; i < n; i++){
      if(i) len += tools_gl2psPrintf(gl2ps," ");
      len += tools_gl2psPrintf(gl2ps,"%d", array[i]);
    }
    len += tools_gl2psPrintf(gl2ps,"] 0 %s\n", str);
  }

  return len;
}

inline void tools_gl2psPrintPostScriptPrimitive(tools_GL2PScontext* gl2ps, void *data)
{
  int newline;
  tools_GL2PSprimitive *prim;

  prim = *(tools_GL2PSprimitive**)data;

  if((gl2ps->options & TOOLS_GL2PS_OCCLUSION_CULL) && prim->culled) return;

  /* Every effort is made to draw lines as connected segments (i.e.,
     using a single PostScript path): this is the only way to get nice
     line joins and to not restart the stippling for every line
     segment. So if the primitive to print is not a line we must first
     finish the current line (if any): */
  if(prim->type != TOOLS_GL2PS_LINE) tools_gl2psEndPostScriptLine(gl2ps);

  switch(prim->type){
  case TOOLS_GL2PS_POINT :
    tools_gl2psPrintPostScriptColor(gl2ps, prim->verts[0].rgba);
    tools_gl2psPrintf(gl2ps,"%g %g %g P\n",
                prim->verts[0].xyz[0], prim->verts[0].xyz[1], 0.5 * prim->width);
    break;
  case TOOLS_GL2PS_LINE :
    if(!tools_gl2psSamePosition(gl2ps->lastvertex.xyz, prim->verts[0].xyz) ||
       !tools_gl2psSameColor(gl2ps->lastrgba, prim->verts[0].rgba) ||
       gl2ps->lastlinewidth != prim->width ||
       gl2ps->lastlinecap != prim->linecap ||
       gl2ps->lastlinejoin != prim->linejoin ||
       gl2ps->lastpattern != prim->pattern ||
       gl2ps->lastfactor != prim->factor){
      /* End the current line if the new segment does not start where
         the last one ended, or if the color, the width or the
         stippling have changed (multi-stroking lines with changing
         colors is necessary until we use /shfill for lines;
         unfortunately this means that at the moment we can screw up
         line stippling for smooth-shaded lines) */
      tools_gl2psEndPostScriptLine(gl2ps);
      newline = 1;
    }
    else{
      newline = 0;
    }
    if(gl2ps->lastlinewidth != prim->width){
      gl2ps->lastlinewidth = prim->width;
      tools_gl2psPrintf(gl2ps,"%g W\n", gl2ps->lastlinewidth);
    }
    if(gl2ps->lastlinecap != prim->linecap){
      gl2ps->lastlinecap = prim->linecap;
      tools_gl2psPrintf(gl2ps,"%d LC\n", gl2ps->lastlinecap);
    }
    if(gl2ps->lastlinejoin != prim->linejoin){
      gl2ps->lastlinejoin = prim->linejoin;
      tools_gl2psPrintf(gl2ps,"%d LJ\n", gl2ps->lastlinejoin);
    }
    tools_gl2psPrintPostScriptDash(gl2ps, prim->pattern, prim->factor, "setdash");
    tools_gl2psPrintPostScriptColor(gl2ps, prim->verts[0].rgba);
    tools_gl2psPrintf(gl2ps,"%g %g %s\n", prim->verts[0].xyz[0], prim->verts[0].xyz[1],
                newline ? "LS" : "L");
    gl2ps->lastvertex = prim->verts[1];
    break;
  case TOOLS_GL2PS_TRIANGLE :
    if(!tools_gl2psVertsSameColor(prim)){
      tools_gl2psResetPostScriptColor(gl2ps);
      tools_gl2psPrintf(gl2ps,"%g %g %g %g %g %g %g %g %g %g %g %g %g %g %g ST\n",
                  prim->verts[2].xyz[0], prim->verts[2].xyz[1],
                  prim->verts[2].rgba[0], prim->verts[2].rgba[1],
                  prim->verts[2].rgba[2], prim->verts[1].xyz[0],
                  prim->verts[1].xyz[1], prim->verts[1].rgba[0],
                  prim->verts[1].rgba[1], prim->verts[1].rgba[2],
                  prim->verts[0].xyz[0], prim->verts[0].xyz[1],
                  prim->verts[0].rgba[0], prim->verts[0].rgba[1],
                  prim->verts[0].rgba[2]);
    }
    else{
      tools_gl2psPrintPostScriptColor(gl2ps, prim->verts[0].rgba);
      tools_gl2psPrintf(gl2ps,"%g %g %g %g %g %g T\n",
                  prim->verts[2].xyz[0], prim->verts[2].xyz[1],
                  prim->verts[1].xyz[0], prim->verts[1].xyz[1],
                  prim->verts[0].xyz[0], prim->verts[0].xyz[1]);
    }
    break;
  case TOOLS_GL2PS_QUADRANGLE :
    tools_gl2psMsg(TOOLS_GL2PS_WARNING, "There should not be any quad left to print");
    break;
  case TOOLS_GL2PS_PIXMAP :
    tools_gl2psPrintPostScriptPixmap(gl2ps, prim->verts[0].xyz[0], prim->verts[0].xyz[1],
                               prim->data.image,0,8); /*G.Barrand: add two last arguments.*/
    break;
  case TOOLS_GL2PS_IMAGEMAP :
    if(prim->data.image->type != TOOLS_GL2PS_IMAGEMAP_WRITTEN){
      tools_gl2psPrintPostScriptColor(gl2ps, prim->verts[0].rgba);
      tools_gl2psPrintPostScriptImagemap(gl2ps, prim->data.image->pixels[0],
                                   prim->data.image->pixels[1],
                                   prim->data.image->width, prim->data.image->height,
                                   (const unsigned char*)(&(prim->data.image->pixels[2])));
      prim->data.image->type = TOOLS_GL2PS_IMAGEMAP_WRITTEN;
    }
    break;
  case TOOLS_GL2PS_TEXT :
    tools_gl2psPrintPostScriptColor(gl2ps, prim->verts[0].rgba);
    tools_gl2psPrintf(gl2ps,"(%s) ", prim->data.text->str);
    if(prim->data.text->angle)
      tools_gl2psPrintf(gl2ps,"%g ", prim->data.text->angle);
    tools_gl2psPrintf(gl2ps,"%g %g %d /%s ",
                prim->verts[0].xyz[0], prim->verts[0].xyz[1],
                prim->data.text->fontsize, prim->data.text->fontname);
    switch(prim->data.text->alignment){
    case TOOLS_GL2PS_TEXT_C:
      tools_gl2psPrintf(gl2ps, prim->data.text->angle ? "SCCR\n" : "SCC\n");
      break;
    case TOOLS_GL2PS_TEXT_CL:
      tools_gl2psPrintf(gl2ps, prim->data.text->angle ? "SCLR\n" : "SCL\n");
      break;
    case TOOLS_GL2PS_TEXT_CR:
      tools_gl2psPrintf(gl2ps, prim->data.text->angle ? "SCRR\n" : "SCR\n");
      break;
    case TOOLS_GL2PS_TEXT_B:
      tools_gl2psPrintf(gl2ps, prim->data.text->angle ? "SBCR\n" : "SBC\n");
      break;
    case TOOLS_GL2PS_TEXT_BR:
      tools_gl2psPrintf(gl2ps, prim->data.text->angle ? "SBRR\n" : "SBR\n");
      break;
    case TOOLS_GL2PS_TEXT_T:
      tools_gl2psPrintf(gl2ps, prim->data.text->angle ? "STCR\n" : "STC\n");
      break;
    case TOOLS_GL2PS_TEXT_TL:
      tools_gl2psPrintf(gl2ps, prim->data.text->angle ? "STLR\n" : "STL\n");
      break;
    case TOOLS_GL2PS_TEXT_TR:
      tools_gl2psPrintf(gl2ps, prim->data.text->angle ? "STRR\n" : "STR\n");
      break;
    case TOOLS_GL2PS_TEXT_BL:
    default:
      tools_gl2psPrintf(gl2ps, prim->data.text->angle ? "SR\n" : "S\n");
      break;
    }
    break;
  case TOOLS_GL2PS_SPECIAL :
    /* alignment contains the format for which the special output text
       is intended */
    if(prim->data.text->alignment == TOOLS_GL2PS_PS ||
       prim->data.text->alignment == TOOLS_GL2PS_EPS)
      tools_gl2psPrintf(gl2ps,"%s\n", prim->data.text->str);
    break;
  default :
    break;
  }
}

inline void tools_gl2psPrintPostScriptFooter(tools_GL2PScontext* gl2ps)
{
  tools_gl2psPrintf(gl2ps,"grestore\n"
              "showpage\n"
              "cleartomark\n"
              "%%%%PageTrailer\n"
              "%%%%Trailer\n"
              "end\n"
              "%%%%EOF\n");

  tools_gl2psPrintGzipFooter(gl2ps);
}

inline void tools_gl2psPrintPostScriptBeginViewport(tools_GL2PScontext* gl2ps, tools_GLint viewport[4])
{
  tools_GLint idx;
  tools_GLfloat rgba[4];
  int x = viewport[0], y = viewport[1], w = viewport[2], h = viewport[3];

  tools_glRenderMode(TOOLS_GL_FEEDBACK);

  if(gl2ps->header){
    tools_gl2psPrintPostScriptHeader(gl2ps);
    gl2ps->header = TOOLS_GL_FALSE;
  }

  tools_gl2psResetPostScriptColor(gl2ps);
  tools_gl2psResetLineProperties(gl2ps);

  tools_gl2psPrintf(gl2ps,"gsave\n"
              "1.0 1.0 scale\n");

  if(gl2ps->options & TOOLS_GL2PS_DRAW_BACKGROUND){
    if(gl2ps->colormode == TOOLS_GL_RGBA || gl2ps->colorsize == 0){
      tools_glGetFloatv(TOOLS_GL_COLOR_CLEAR_VALUE, rgba);
    }
    else{
      tools_glGetIntegerv(TOOLS_GL_INDEX_CLEAR_VALUE, &idx);
      rgba[0] = gl2ps->colormap[idx][0];
      rgba[1] = gl2ps->colormap[idx][1];
      rgba[2] = gl2ps->colormap[idx][2];
      rgba[3] = 1.0F;
    }
    tools_gl2psPrintf(gl2ps,"%g %g %g C\n"
                "newpath %d %d moveto %d %d lineto %d %d lineto %d %d lineto\n"
                "closepath fill\n",
                rgba[0], rgba[1], rgba[2],
                x, y, x+w, y, x+w, y+h, x, y+h);
  }

  tools_gl2psPrintf(gl2ps,"newpath %d %d moveto %d %d lineto %d %d lineto %d %d lineto\n"
              "closepath clip\n",
              x, y, x+w, y, x+w, y+h, x, y+h);

}

inline tools_GLint tools_gl2psPrintPostScriptEndViewport(tools_GL2PScontext* gl2ps)
{
  tools_GLint res;

  res = tools_gl2psPrintPrimitives(gl2ps);
  tools_gl2psPrintf(gl2ps,"grestore\n");
  return res;
}

inline void tools_gl2psPrintPostScriptFinalPrimitive(tools_GL2PScontext* gl2ps)
{
  /* End any remaining line, if any */
  tools_gl2psEndPostScriptLine(gl2ps);
}

/* definition of the PostScript and Encapsulated PostScript backends */

static const tools_GL2PSbackend tools_gl2psPS = {
  tools_gl2psPrintPostScriptHeader,
  tools_gl2psPrintPostScriptFooter,
  tools_gl2psPrintPostScriptBeginViewport,
  tools_gl2psPrintPostScriptEndViewport,
  tools_gl2psPrintPostScriptPrimitive,
  tools_gl2psPrintPostScriptFinalPrimitive,
  "ps",
  "Postscript"
};

static const tools_GL2PSbackend tools_gl2psEPS = {
  tools_gl2psPrintPostScriptHeader,
  tools_gl2psPrintPostScriptFooter,
  tools_gl2psPrintPostScriptBeginViewport,
  tools_gl2psPrintPostScriptEndViewport,
  tools_gl2psPrintPostScriptPrimitive,
  tools_gl2psPrintPostScriptFinalPrimitive,
  "eps",
  "Encapsulated Postscript"
};

/*********************************************************************
 *
 * LaTeX routines
 *
 *********************************************************************/

inline void tools_gl2psPrintTeXHeader(tools_GL2PScontext* gl2ps)
{
  char name[256];
  time_t now;
  int i;
  tools_GLfloat _s;

  if(gl2ps->filename && strlen(gl2ps->filename) < 256){
    for(i = (int)strlen(gl2ps->filename) - 1; i >= 0; i--){
      if(gl2ps->filename[i] == '.'){
        strncpy(name, gl2ps->filename, i);
        name[i] = '\0';
        break;
      }
    }
    if(i <= 0) strcpy(name, gl2ps->filename);
  }
  else{
    strcpy(name, "untitled");
  }

  time(&now);

  fprintf(gl2ps->stream,
          "%% Title: %s\n"
          "%% Creator: GL2PS %d.%d.%d%s, %s\n"
          "%% For: %s\n"
          "%% CreationDate: %s",
          gl2ps->title, TOOLS_GL2PS_MAJOR_VERSION, TOOLS_GL2PS_MINOR_VERSION,
          TOOLS_GL2PS_PATCH_VERSION, TOOLS_GL2PS_EXTRA_VERSION, TOOLS_GL2PS_COPYRIGHT,
          gl2ps->producer, ctime(&now));

  _s = gl2ps->tex_scaling;
  if(_s <= 0.) _s = 1.;
  fprintf(gl2ps->stream,
          "\\setlength{\\unitlength}{%gpt}\n"
          "\\begin{picture}(0,0)\n"
          "\\includegraphics[scale=%g]{%s}\n"
          "\\end{picture}%%\n"
          "%s\\begin{picture}(%d,%d)(0,0)\n",
          _s, _s, name,
          (gl2ps->options & TOOLS_GL2PS_LANDSCAPE) ? "\\rotatebox{90}{" : "",
          (int)(gl2ps->viewport[2]), (int)(gl2ps->viewport[3]));
}

inline void tools_gl2psPrintTeXPrimitive(tools_GL2PScontext* gl2ps, void *data)
{
  tools_GL2PSprimitive *prim;

  prim = *(tools_GL2PSprimitive**)data;

  switch(prim->type){
  case TOOLS_GL2PS_TEXT :
    if(!(gl2ps->options & TOOLS_GL2PS_NO_TEX_FONTSIZE))
      fprintf(gl2ps->stream, "\\fontsize{%d}{0}\\selectfont",
              prim->data.text->fontsize);
    fprintf(gl2ps->stream, "\\put(%g,%g)",
            prim->verts[0].xyz[0],
            prim->verts[0].xyz[1]);
    if(prim->data.text->angle)
      fprintf(gl2ps->stream, "{\\rotatebox{%g}", prim->data.text->angle);
    fprintf(gl2ps->stream, "{\\makebox(0,0)");
    switch(prim->data.text->alignment){
    case TOOLS_GL2PS_TEXT_C:
      fprintf(gl2ps->stream, "{");
      break;
    case TOOLS_GL2PS_TEXT_CL:
      fprintf(gl2ps->stream, "[l]{");
      break;
    case TOOLS_GL2PS_TEXT_CR:
      fprintf(gl2ps->stream, "[r]{");
      break;
    case TOOLS_GL2PS_TEXT_B:
      fprintf(gl2ps->stream, "[b]{");
      break;
    case TOOLS_GL2PS_TEXT_BR:
      fprintf(gl2ps->stream, "[br]{");
      break;
    case TOOLS_GL2PS_TEXT_T:
      fprintf(gl2ps->stream, "[t]{");
      break;
    case TOOLS_GL2PS_TEXT_TL:
      fprintf(gl2ps->stream, "[tl]{");
      break;
    case TOOLS_GL2PS_TEXT_TR:
      fprintf(gl2ps->stream, "[tr]{");
      break;
    case TOOLS_GL2PS_TEXT_BL:
    default:
      fprintf(gl2ps->stream, "[bl]{");
      break;
    }
    fprintf(gl2ps->stream, "\\textcolor[rgb]{%g,%g,%g}{{%s}}",
            prim->verts[0].rgba[0], prim->verts[0].rgba[1], prim->verts[0].rgba[2],
            prim->data.text->str);
    if(prim->data.text->angle)
      fprintf(gl2ps->stream, "}");
    fprintf(gl2ps->stream, "}}\n");
    break;
  case TOOLS_GL2PS_SPECIAL :
    /* alignment contains the format for which the special output text
       is intended */
    if (prim->data.text->alignment == TOOLS_GL2PS_TEX)
      fprintf(gl2ps->stream, "%s\n", prim->data.text->str);
    break;
  default :
    break;
  }
}

inline void tools_gl2psPrintTeXFooter(tools_GL2PScontext* gl2ps)
{
  fprintf(gl2ps->stream, "\\end{picture}%s\n",
          (gl2ps->options & TOOLS_GL2PS_LANDSCAPE) ? "}" : "");
}

inline void tools_gl2psPrintTeXBeginViewport(tools_GL2PScontext* gl2ps, tools_GLint viewport[4])
{
  (void) viewport;  /* not used */
  tools_glRenderMode(TOOLS_GL_FEEDBACK);

  tools_gl2psResetLineProperties(gl2ps);

  if(gl2ps->header){
    tools_gl2psPrintTeXHeader(gl2ps);
    gl2ps->header = TOOLS_GL_FALSE;
  }
}

inline tools_GLint tools_gl2psPrintTeXEndViewport(tools_GL2PScontext* gl2ps)
{
  return tools_gl2psPrintPrimitives(gl2ps);
}

inline void tools_gl2psPrintTeXFinalPrimitive(tools_GL2PScontext*)
{
}

/* definition of the LaTeX backend */

static const tools_GL2PSbackend tools_gl2psTEX = {
  tools_gl2psPrintTeXHeader,
  tools_gl2psPrintTeXFooter,
  tools_gl2psPrintTeXBeginViewport,
  tools_gl2psPrintTeXEndViewport,
  tools_gl2psPrintTeXPrimitive,
  tools_gl2psPrintTeXFinalPrimitive,
  "tex",
  "LaTeX text"
};

/*********************************************************************
 *
 * PDF routines
 *
 *********************************************************************/

inline int tools_gl2psPrintPDFCompressorType(tools_GL2PScontext* gl2ps)
{
#if defined(TOOLS_GL2PS_HAVE_ZLIB)
  if(gl2ps->options & TOOLS_GL2PS_COMPRESS){
    return fprintf(gl2ps->stream, "/Filter [/FlateDecode]\n");
  }
#endif
  (void)gl2ps;
  return 0;
}

inline int tools_gl2psPrintPDFStrokeColor(tools_GL2PScontext* gl2ps, tools_GL2PSrgba rgba)
{
  int i, offs = 0;

  tools_gl2psSetLastColor(gl2ps, rgba);
  for(i = 0; i < 3; ++i){
    if(TOOLS_GL2PS_ZERO(rgba[i]))
      offs += tools_gl2psPrintf(gl2ps,"%.0f ", 0.);
    else if(rgba[i] < 1e-4 || rgba[i] > 1e6) /* avoid %e formatting */
      offs += tools_gl2psPrintf(gl2ps,"%f ", rgba[i]);
    else
      offs += tools_gl2psPrintf(gl2ps,"%g ", rgba[i]);
  }
  offs += tools_gl2psPrintf(gl2ps,"RG\n");
  return offs;
}

inline int tools_gl2psPrintPDFFillColor(tools_GL2PScontext* gl2ps, tools_GL2PSrgba rgba)
{
  int i, offs = 0;

  for(i = 0; i < 3; ++i){
    if(TOOLS_GL2PS_ZERO(rgba[i]))
      offs += tools_gl2psPrintf(gl2ps,"%.0f ", 0.);
    else if(rgba[i] < 1e-4 || rgba[i] > 1e6) /* avoid %e formatting */
      offs += tools_gl2psPrintf(gl2ps,"%f ", rgba[i]);
    else
      offs += tools_gl2psPrintf(gl2ps,"%g ", rgba[i]);
  }
  offs += tools_gl2psPrintf(gl2ps,"rg\n");
  return offs;
}

inline int tools_gl2psPrintPDFLineWidth(tools_GL2PScontext* gl2ps, tools_GLfloat lw)
{
  if(TOOLS_GL2PS_ZERO(lw))
    return tools_gl2psPrintf(gl2ps,"%.0f w\n", 0.);
  else if(lw < 1e-4 || lw > 1e6) /* avoid %e formatting */
    return tools_gl2psPrintf(gl2ps,"%f w\n", lw);
  else
    return tools_gl2psPrintf(gl2ps,"%g w\n", lw);
}

inline int tools_gl2psPrintPDFLineCap(tools_GL2PScontext* gl2ps, tools_GLint lc)
{
  if(gl2ps->lastlinecap == lc)
    return 0;
  else
    return tools_gl2psPrintf(gl2ps,"%d J\n", lc);
}

inline int tools_gl2psPrintPDFLineJoin(tools_GL2PScontext* gl2ps, tools_GLint lj)
{
  if(gl2ps->lastlinejoin == lj)
    return 0;
  else
    return tools_gl2psPrintf(gl2ps,"%d j\n", lj);
}

inline void tools_gl2psPutPDFText(tools_GL2PScontext* gl2ps, tools_GL2PSstring *text, int cnt, tools_GLfloat x, tools_GLfloat y)
{
  tools_GLfloat _rad, crad, srad;

  if(text->angle == 0.0F){
    gl2ps->streamlength += tools_gl2psPrintf
      (gl2ps, "BT\n"
       "/F%d %d Tf\n"
       "%f %f Td\n"
       "(%s) Tj\n"
       "ET\n",
       cnt, text->fontsize, x, y, text->str);
  }
  else{
    _rad = (tools_GLfloat)(3.141593F * text->angle / 180.0F);
    srad = (tools_GLfloat)sin(_rad);
    crad = (tools_GLfloat)cos(_rad);
    gl2ps->streamlength += tools_gl2psPrintf
      (gl2ps, "BT\n"
       "/F%d %d Tf\n"
       "%f %f %f %f %f %f Tm\n"
       "(%s) Tj\n"
       "ET\n",
       cnt, text->fontsize, crad, srad, -srad, crad, x, y, text->str);
  }
}

/*
  This is used for producing aligned text in PDF. (x, y) is the anchor for the
  aligned text, (xbl, ybl) is the bottom left corner. Rotation happens
  around (x, y).*/
inline void tools_gl2psPutPDFTextBL(tools_GL2PScontext* gl2ps, tools_GL2PSstring *text, int cnt, tools_GLfloat x, tools_GLfloat y,
                              tools_GLfloat xbl, tools_GLfloat ybl)
{
  if(text->angle == 0.0F){
    gl2ps->streamlength += tools_gl2psPrintf
      (gl2ps, "BT\n"
       "/F%d %d Tf\n"
       "%f %f Td\n"
       "(%s) Tj\n"
       "ET\n",
       cnt, text->fontsize, xbl, ybl, text->str);
  }
  else{
    tools_GLfloat a, ca, sa;
    tools_GLfloat pi = 3.141593F;
    tools_GLfloat i = atan2(y - ybl, x - xbl);
    tools_GLfloat r = sqrt((y - ybl) * (y - ybl) + (x - xbl) * (x - xbl));

    a = (tools_GLfloat)(pi * text->angle / 180.0F);
    sa = (tools_GLfloat)sin(a);
    ca = (tools_GLfloat)cos(a);
    gl2ps->streamlength += tools_gl2psPrintf
      (gl2ps, "BT\n"
       "/F%d %d Tf\n"
       "%f %f %f %f %f %f Tm\n"
       "(%s) Tj\n"
       "ET\n",
       cnt, text->fontsize,
       ca, sa, -sa, ca,
       xbl + r * (cos(i) - cos(i + a)), ybl + r * (sin(i) - sin(i+a)), text->str);
  }
}

inline void tools_gl2psPutPDFSpecial(tools_GL2PScontext* gl2ps, int prim, int sec, tools_GL2PSstring *text)
{
  gl2ps->streamlength += tools_gl2psPrintf(gl2ps,"/GS%d%d gs\n", prim, sec);
  gl2ps->streamlength += tools_gl2psPrintf(gl2ps,"%s\n", text->str);
}

inline void tools_gl2psPutPDFImage(tools_GL2PScontext* gl2ps, tools_GL2PSimage *image, int cnt, tools_GLfloat x, tools_GLfloat y)
{
  gl2ps->streamlength += tools_gl2psPrintf
    (gl2ps, "q\n"
     "%d 0 0 %d %f %f cm\n"
     "/Im%d Do\n"
     "Q\n",
     (int)(image->zoom_x * image->width), (int)(image->zoom_y * image->height),
     x, y, cnt);
}

inline void tools_gl2psPDFstacksInit(tools_GL2PScontext* gl2ps)
{
  gl2ps->objects_stack = 7 /* FIXED_XREF_ENTRIES */ + 1;
  gl2ps->extgs_stack = 0;
  gl2ps->font_stack = 0;
  gl2ps->im_stack = 0;
  gl2ps->trgroupobjects_stack = 0;
  gl2ps->shader_stack = 0;
  gl2ps->mshader_stack = 0;
}

inline void tools_gl2psPDFgroupObjectInit(tools_GL2PSpdfgroup *gro)
{
  if(!gro)
    return;

  gro->ptrlist = NULL;
  gro->fontno = gro->gsno = gro->imno = gro->maskshno = gro->shno
    = gro->trgroupno = gro->fontobjno = gro->imobjno = gro->shobjno
    = gro->maskshobjno = gro->gsobjno = gro->trgroupobjno = -1;
}

/* Build up group objects and assign name and object numbers */

inline void tools_gl2psPDFgroupListInit(tools_GL2PScontext* gl2ps)
{
  int i;
  tools_GL2PSprimitive *p = NULL;
  tools_GL2PSpdfgroup gro;
  int lasttype = TOOLS_GL2PS_NO_TYPE;
  tools_GL2PSrgba lastrgba = {-1.0F, -1.0F, -1.0F, -1.0F};
  tools_GLushort lastpattern = 0;
  tools_GLint lastfactor = 0;
  tools_GLfloat lastwidth = 1;
  tools_GLint lastlinecap = 0;
  tools_GLint lastlinejoin = 0;
  tools_GL2PStriangle lastt, tmpt;
  int lastTriangleWasNotSimpleWithSameColor = 0;

  if(!gl2ps->pdfprimlist)
    return;

  /*G.Barrand: add the below line to quiet Coverity about gro.ptrlist not inited
               in the below TOOLS_GL2PS_LINE, TOOLS_GL2PS_POINT cases.*/
  tools_gl2psPDFgroupObjectInit(&gro);
  
  gl2ps->pdfgrouplist = tools_gl2psListCreate(500, 500, sizeof(tools_GL2PSpdfgroup));
  tools_gl2psInitTriangle(&lastt);

  for(i = 0; i < tools_gl2psListNbr(gl2ps->pdfprimlist); ++i){
    p = *(tools_GL2PSprimitive**)tools_gl2psListPointer(gl2ps->pdfprimlist, i);
    switch(p->type){
    case TOOLS_GL2PS_PIXMAP:
      tools_gl2psPDFgroupObjectInit(&gro);
      gro.ptrlist = tools_gl2psListCreate(1, 2, sizeof(tools_GL2PSprimitive*));
      gro.imno = gl2ps->im_stack++;
      tools_gl2psListAdd(gro.ptrlist, &p);
      tools_gl2psListAdd(gl2ps->pdfgrouplist, &gro);
      break;
    case TOOLS_GL2PS_TEXT:
      tools_gl2psPDFgroupObjectInit(&gro);
      gro.ptrlist = tools_gl2psListCreate(1, 2, sizeof(tools_GL2PSprimitive*));
      gro.fontno = gl2ps->font_stack++;
      tools_gl2psListAdd(gro.ptrlist, &p);
      tools_gl2psListAdd(gl2ps->pdfgrouplist, &gro);
      break;
    case TOOLS_GL2PS_LINE:
      if(lasttype != p->type || lastwidth != p->width ||
         lastlinecap != p->linecap || lastlinejoin != p->linejoin ||
         lastpattern != p->pattern || lastfactor != p->factor ||
         !tools_gl2psSameColor(p->verts[0].rgba, lastrgba)){
        tools_gl2psPDFgroupObjectInit(&gro);
        gro.ptrlist = tools_gl2psListCreate(1, 2, sizeof(tools_GL2PSprimitive*));
        tools_gl2psListAdd(gro.ptrlist, &p);
        tools_gl2psListAdd(gl2ps->pdfgrouplist, &gro);
      }
      else{
        tools_gl2psListAdd(gro.ptrlist, &p);
      }
      lastpattern = p->pattern;
      lastfactor = p->factor;
      lastwidth = p->width;
      lastlinecap = p->linecap;
      lastlinejoin = p->linejoin;
      lastrgba[0] = p->verts[0].rgba[0];
      lastrgba[1] = p->verts[0].rgba[1];
      lastrgba[2] = p->verts[0].rgba[2];
      break;
    case TOOLS_GL2PS_POINT:
      if(lasttype != p->type || lastwidth != p->width ||
         !tools_gl2psSameColor(p->verts[0].rgba, lastrgba)){
        tools_gl2psPDFgroupObjectInit(&gro);
        gro.ptrlist = tools_gl2psListCreate(1,2,sizeof(tools_GL2PSprimitive*));
        tools_gl2psListAdd(gro.ptrlist, &p);
        tools_gl2psListAdd(gl2ps->pdfgrouplist, &gro);
      }
      else{
        tools_gl2psListAdd(gro.ptrlist, &p);
      }
      lastwidth = p->width;
      lastrgba[0] = p->verts[0].rgba[0];
      lastrgba[1] = p->verts[0].rgba[1];
      lastrgba[2] = p->verts[0].rgba[2];
      break;
    case TOOLS_GL2PS_TRIANGLE:
      tools_gl2psFillTriangleFromPrimitive(&tmpt, p, TOOLS_GL_TRUE);
      lastTriangleWasNotSimpleWithSameColor =
        !(tmpt.prop & T_CONST_COLOR && tmpt.prop & T_ALPHA_1) ||
        !tools_gl2psSameColor(tmpt.vertex[0].rgba, lastt.vertex[0].rgba);
      if(lasttype == p->type && tmpt.prop == lastt.prop &&
         lastTriangleWasNotSimpleWithSameColor){
        /* TODO Check here for last alpha */
        tools_gl2psListAdd(gro.ptrlist, &p);
      }
      else{
        tools_gl2psPDFgroupObjectInit(&gro);
        gro.ptrlist = tools_gl2psListCreate(1, 2, sizeof(tools_GL2PSprimitive*));
        tools_gl2psListAdd(gro.ptrlist, &p);
        tools_gl2psListAdd(gl2ps->pdfgrouplist, &gro);
      }
      lastt = tmpt;
      break;
    case TOOLS_GL2PS_SPECIAL:
      tools_gl2psPDFgroupObjectInit(&gro);
      gro.ptrlist = tools_gl2psListCreate(1, 2, sizeof(tools_GL2PSprimitive*));
      tools_gl2psListAdd(gro.ptrlist, &p);
      tools_gl2psListAdd(gl2ps->pdfgrouplist, &gro);
      break;
    default:
      break;
    }
    lasttype = p->type;
  }
}

inline void tools_gl2psSortOutTrianglePDFgroup(tools_GL2PScontext* gl2ps, tools_GL2PSpdfgroup *gro)
{
  tools_GL2PStriangle t;
  tools_GL2PSprimitive *prim = NULL;

  if(!gro)
    return;

  if(!tools_gl2psListNbr(gro->ptrlist))
    return;

  prim = *(tools_GL2PSprimitive**)tools_gl2psListPointer(gro->ptrlist, 0);

  if(prim->type != TOOLS_GL2PS_TRIANGLE)
    return;

  tools_gl2psFillTriangleFromPrimitive(&t, prim, TOOLS_GL_TRUE);

  if(t.prop & T_CONST_COLOR && t.prop & T_ALPHA_LESS_1){
    gro->gsno = gl2ps->extgs_stack++;
    gro->gsobjno = gl2ps->objects_stack ++;
  }
  else if(t.prop & T_CONST_COLOR && t.prop & T_VAR_ALPHA){
    gro->gsno = gl2ps->extgs_stack++;
    gro->gsobjno = gl2ps->objects_stack++;
    gro->trgroupno = gl2ps->trgroupobjects_stack++;
    gro->trgroupobjno = gl2ps->objects_stack++;
    gro->maskshno = gl2ps->mshader_stack++;
    gro->maskshobjno = gl2ps->objects_stack++;
  }
  else if(t.prop & T_VAR_COLOR && t.prop & T_ALPHA_1){
    gro->shno = gl2ps->shader_stack++;
    gro->shobjno = gl2ps->objects_stack++;
  }
  else if(t.prop & T_VAR_COLOR && t.prop & T_ALPHA_LESS_1){
    gro->gsno = gl2ps->extgs_stack++;
    gro->gsobjno = gl2ps->objects_stack++;
    gro->shno = gl2ps->shader_stack++;
    gro->shobjno = gl2ps->objects_stack++;
  }
  else if(t.prop & T_VAR_COLOR && t.prop & T_VAR_ALPHA){
    gro->gsno = gl2ps->extgs_stack++;
    gro->gsobjno = gl2ps->objects_stack++;
    gro->shno = gl2ps->shader_stack++;
    gro->shobjno = gl2ps->objects_stack++;
    gro->trgroupno = gl2ps->trgroupobjects_stack++;
    gro->trgroupobjno = gl2ps->objects_stack++;
    gro->maskshno = gl2ps->mshader_stack++;
    gro->maskshobjno = gl2ps->objects_stack++;
  }
}

/* Main stream data */

inline void tools_gl2psPDFgroupListWriteMainStream(tools_GL2PScontext* gl2ps)
{
  int i, j, lastel, count;
  tools_GL2PSprimitive *prim = NULL, *prev = NULL;
  tools_GL2PSpdfgroup *gro;
  tools_GL2PStriangle t;

  if(!gl2ps->pdfgrouplist)
    return;

  count = tools_gl2psListNbr(gl2ps->pdfgrouplist);

  for(i = 0; i < count; ++i){
    gro = (tools_GL2PSpdfgroup*)tools_gl2psListPointer(gl2ps->pdfgrouplist, i);

    lastel = tools_gl2psListNbr(gro->ptrlist) - 1;
    if(lastel < 0)
      continue;

    prim = *(tools_GL2PSprimitive**)tools_gl2psListPointer(gro->ptrlist, 0);

    switch(prim->type){
    case TOOLS_GL2PS_POINT:
      gl2ps->streamlength += tools_gl2psPrintf(gl2ps,"1 J\n");
      gl2ps->streamlength += tools_gl2psPrintPDFLineWidth(gl2ps, prim->width);
      gl2ps->streamlength += tools_gl2psPrintPDFStrokeColor(gl2ps, prim->verts[0].rgba);
      for(j = 0; j <= lastel; ++j){
        prim = *(tools_GL2PSprimitive**)tools_gl2psListPointer(gro->ptrlist, j);
        gl2ps->streamlength +=
          tools_gl2psPrintf(gl2ps,"%f %f m %f %f l\n",
                      prim->verts[0].xyz[0], prim->verts[0].xyz[1],
                      prim->verts[0].xyz[0], prim->verts[0].xyz[1]);
      }
      gl2ps->streamlength += tools_gl2psPrintf(gl2ps,"S\n");
      gl2ps->streamlength += tools_gl2psPrintf(gl2ps,"0 J\n");
      break;
    case TOOLS_GL2PS_LINE:
      /* We try to use as few paths as possible to draw lines, in
         order to get nice stippling even when the individual segments
         are smaller than the stipple */
      gl2ps->streamlength += tools_gl2psPrintPDFLineWidth(gl2ps, prim->width);
      gl2ps->streamlength += tools_gl2psPrintPDFLineCap(gl2ps, prim->linecap);
      gl2ps->streamlength += tools_gl2psPrintPDFLineJoin(gl2ps, prim->linejoin);
      gl2ps->streamlength += tools_gl2psPrintPDFStrokeColor(gl2ps, prim->verts[0].rgba);
      gl2ps->streamlength += tools_gl2psPrintPostScriptDash(gl2ps, prim->pattern, prim->factor, "d");
      /* start new path */
      gl2ps->streamlength +=
        tools_gl2psPrintf(gl2ps,"%f %f m\n",
                    prim->verts[0].xyz[0], prim->verts[0].xyz[1]);

      for(j = 1; j <= lastel; ++j){
        prev = prim;
        prim = *(tools_GL2PSprimitive**)tools_gl2psListPointer(gro->ptrlist, j);
        if(!tools_gl2psSamePosition(prim->verts[0].xyz, prev->verts[1].xyz)){
          /* the starting point of the new segment does not match the
             end point of the previous line, so we end the current
             path and start a new one */
          gl2ps->streamlength +=
            tools_gl2psPrintf(gl2ps,"%f %f l\n",
                        prev->verts[1].xyz[0], prev->verts[1].xyz[1]);
          gl2ps->streamlength +=
            tools_gl2psPrintf(gl2ps,"%f %f m\n",
                        prim->verts[0].xyz[0], prim->verts[0].xyz[1]);
        }
        else{
          /* the two segements are connected, so we just append to the
             current path */
          gl2ps->streamlength +=
            tools_gl2psPrintf(gl2ps,"%f %f l\n",
                        prim->verts[0].xyz[0], prim->verts[0].xyz[1]);
        }
      }
      /* end last path */
      gl2ps->streamlength +=
        tools_gl2psPrintf(gl2ps,"%f %f l\n",
                    prim->verts[1].xyz[0], prim->verts[1].xyz[1]);
      gl2ps->streamlength += tools_gl2psPrintf(gl2ps,"S\n");
      break;
    case TOOLS_GL2PS_TRIANGLE:
      tools_gl2psFillTriangleFromPrimitive(&t, prim, TOOLS_GL_TRUE);
      tools_gl2psSortOutTrianglePDFgroup(gl2ps, gro);

      /* No alpha and const color: Simple PDF draw orders  */
      if(t.prop & T_CONST_COLOR && t.prop & T_ALPHA_1){
        gl2ps->streamlength += tools_gl2psPrintPDFFillColor(gl2ps, t.vertex[0].rgba);
        for(j = 0; j <= lastel; ++j){
          prim = *(tools_GL2PSprimitive**)tools_gl2psListPointer(gro->ptrlist, j);
          tools_gl2psFillTriangleFromPrimitive(&t, prim, TOOLS_GL_FALSE);
          gl2ps->streamlength
            += tools_gl2psPrintf(gl2ps,"%f %f m\n"
                           "%f %f l\n"
                           "%f %f l\n"
                           "h f\n",
                           t.vertex[0].xyz[0], t.vertex[0].xyz[1],
                           t.vertex[1].xyz[0], t.vertex[1].xyz[1],
                           t.vertex[2].xyz[0], t.vertex[2].xyz[1]);
        }
      }
      /* Const alpha < 1 and const color: Simple PDF draw orders
         and an extra extended Graphics State for the alpha const */
      else if(t.prop & T_CONST_COLOR && t.prop & T_ALPHA_LESS_1){
        gl2ps->streamlength += tools_gl2psPrintf(gl2ps,"q\n"
                                           "/GS%d gs\n",
                                           gro->gsno);
        gl2ps->streamlength += tools_gl2psPrintPDFFillColor(gl2ps, prim->verts[0].rgba);
        for(j = 0; j <= lastel; ++j){
          prim = *(tools_GL2PSprimitive**)tools_gl2psListPointer(gro->ptrlist, j);
          tools_gl2psFillTriangleFromPrimitive(&t, prim, TOOLS_GL_FALSE);
          gl2ps->streamlength
            += tools_gl2psPrintf(gl2ps,"%f %f m\n"
                           "%f %f l\n"
                           "%f %f l\n"
                           "h f\n",
                           t.vertex[0].xyz[0], t.vertex[0].xyz[1],
                           t.vertex[1].xyz[0], t.vertex[1].xyz[1],
                           t.vertex[2].xyz[0], t.vertex[2].xyz[1]);
        }
        gl2ps->streamlength += tools_gl2psPrintf(gl2ps,"Q\n");
      }
      /* Variable alpha and const color: Simple PDF draw orders
         and an extra extended Graphics State + Xobject + Shader
         object for the alpha mask */
      else if(t.prop & T_CONST_COLOR && t.prop & T_VAR_ALPHA){
        gl2ps->streamlength += tools_gl2psPrintf(gl2ps,"q\n"
                                           "/GS%d gs\n"
                                           "/TrG%d Do\n",
                                           gro->gsno, gro->trgroupno);
        gl2ps->streamlength += tools_gl2psPrintPDFFillColor(gl2ps, prim->verts[0].rgba);
        for(j = 0; j <= lastel; ++j){
          prim = *(tools_GL2PSprimitive**)tools_gl2psListPointer(gro->ptrlist, j);
          tools_gl2psFillTriangleFromPrimitive(&t, prim, TOOLS_GL_FALSE);
          gl2ps->streamlength
            += tools_gl2psPrintf(gl2ps,"%f %f m\n"
                           "%f %f l\n"
                           "%f %f l\n"
                           "h f\n",
                           t.vertex[0].xyz[0], t.vertex[0].xyz[1],
                           t.vertex[1].xyz[0], t.vertex[1].xyz[1],
                           t.vertex[2].xyz[0], t.vertex[2].xyz[1]);
        }
        gl2ps->streamlength += tools_gl2psPrintf(gl2ps,"Q\n");
      }
      /* Variable color and no alpha: Shader Object for the colored
         triangle(s) */
      else if(t.prop & T_VAR_COLOR && t.prop & T_ALPHA_1){
        gl2ps->streamlength += tools_gl2psPrintf(gl2ps,"/Sh%d sh\n", gro->shno);
      }
      /* Variable color and const alpha < 1: Shader Object for the
         colored triangle(s) and an extra extended Graphics State
         for the alpha const */
      else if(t.prop & T_VAR_COLOR && t.prop & T_ALPHA_LESS_1){
        gl2ps->streamlength += tools_gl2psPrintf(gl2ps,"q\n"
                                           "/GS%d gs\n"
                                           "/Sh%d sh\n"
                                           "Q\n",
                                           gro->gsno, gro->shno);
      }
      /* Variable alpha and color: Shader Object for the colored
         triangle(s) and an extra extended Graphics State
         + Xobject + Shader object for the alpha mask */
      else if(t.prop & T_VAR_COLOR && t.prop & T_VAR_ALPHA){
        gl2ps->streamlength += tools_gl2psPrintf(gl2ps,"q\n"
                                           "/GS%d gs\n"
                                           "/TrG%d Do\n"
                                           "/Sh%d sh\n"
                                           "Q\n",
                                           gro->gsno, gro->trgroupno, gro->shno);
      }
      break;
    case TOOLS_GL2PS_PIXMAP:
      for(j = 0; j <= lastel; ++j){
        prim = *(tools_GL2PSprimitive**)tools_gl2psListPointer(gro->ptrlist, j);
        tools_gl2psPutPDFImage(gl2ps, prim->data.image, gro->imno, prim->verts[0].xyz[0],
                         prim->verts[0].xyz[1]);
      }
      break;
    case TOOLS_GL2PS_TEXT:
      for(j = 0; j <= lastel; ++j){
        prim = *(tools_GL2PSprimitive**)tools_gl2psListPointer(gro->ptrlist, j);
        gl2ps->streamlength += tools_gl2psPrintPDFFillColor(gl2ps, prim->verts[0].rgba);
        if (prim->numverts == 2) {
          tools_gl2psPutPDFTextBL(gl2ps, prim->data.text, gro->fontno, prim->verts[0].xyz[0],
                            prim->verts[0].xyz[1],
                            prim->verts[1].xyz[0],
                            prim->verts[1].xyz[1]);
        }
        else {
          tools_gl2psPutPDFText(gl2ps, prim->data.text, gro->fontno, prim->verts[0].xyz[0],
                          prim->verts[0].xyz[1]);
        }
      }
      break;
    case TOOLS_GL2PS_SPECIAL:
      lastel = tools_gl2psListNbr(gro->ptrlist) - 1;
      if(lastel < 0)
        continue;

      for(j = 0; j <= lastel; ++j){
        prim = *(tools_GL2PSprimitive**)tools_gl2psListPointer(gro->ptrlist, j);
        tools_gl2psPutPDFSpecial(gl2ps, i, j, prim->data.text);
      }
    default:
      break;
    }
  }
}

/* Graphics State names */

inline int tools_gl2psPDFgroupListWriteGStateResources(tools_GL2PScontext* gl2ps)
{
  tools_GL2PSpdfgroup *gro;
  int offs = 0;
  int i;

  offs += fprintf(gl2ps->stream,
                  "/ExtGState\n"
                  "<<\n"
                  "/GSa 7 0 R\n");
  for(i = 0; i < tools_gl2psListNbr(gl2ps->pdfgrouplist); ++i){
    gro = (tools_GL2PSpdfgroup*)tools_gl2psListPointer(gl2ps->pdfgrouplist, i);
    if(gro->gsno >= 0)
      offs += fprintf(gl2ps->stream, "/GS%d %d 0 R\n", gro->gsno, gro->gsobjno);
  }
  offs += fprintf(gl2ps->stream, ">>\n");
  return offs;
}

/* Main Shader names */

inline int tools_gl2psPDFgroupListWriteShaderResources(tools_GL2PScontext* gl2ps)
{
  tools_GL2PSpdfgroup *gro;
  int offs = 0;
  int i;

  offs += fprintf(gl2ps->stream,
                  "/Shading\n"
                  "<<\n");
  for(i = 0; i < tools_gl2psListNbr(gl2ps->pdfgrouplist); ++i){
    gro = (tools_GL2PSpdfgroup*)tools_gl2psListPointer(gl2ps->pdfgrouplist, i);
    if(gro->shno >= 0)
      offs += fprintf(gl2ps->stream, "/Sh%d %d 0 R\n", gro->shno, gro->shobjno);
    if(gro->maskshno >= 0)
      offs += fprintf(gl2ps->stream, "/TrSh%d %d 0 R\n", gro->maskshno, gro->maskshobjno);
  }
  offs += fprintf(gl2ps->stream,">>\n");
  return offs;
}

/* Images & Mask Shader XObject names */
inline int tools_gl2psPDFgroupListWriteXObjectResources(tools_GL2PScontext* gl2ps)
{
  int i;
  tools_GL2PSprimitive *p = NULL;
  tools_GL2PSpdfgroup *gro;
  int offs = 0;

  offs += fprintf(gl2ps->stream,
                  "/XObject\n"
                  "<<\n");

  for(i = 0; i < tools_gl2psListNbr(gl2ps->pdfgrouplist); ++i){
    gro = (tools_GL2PSpdfgroup*)tools_gl2psListPointer(gl2ps->pdfgrouplist, i);
    if(!tools_gl2psListNbr(gro->ptrlist))
      continue;
    p = *(tools_GL2PSprimitive**)tools_gl2psListPointer(gro->ptrlist, 0);
    switch(p->type){
    case TOOLS_GL2PS_PIXMAP:
      gro->imobjno = gl2ps->objects_stack++;
      if(TOOLS_GL_RGBA == p->data.image->format)  /* reserve one object for image mask */
        gl2ps->objects_stack++;
      offs += fprintf(gl2ps->stream, "/Im%d %d 0 R\n", gro->imno, gro->imobjno);
      break; /*G.Barrand : add this break.*/
    case TOOLS_GL2PS_TRIANGLE:
      if(gro->trgroupno >=0)
        offs += fprintf(gl2ps->stream, "/TrG%d %d 0 R\n", gro->trgroupno, gro->trgroupobjno);
      break;
    default:
      break;
    }
  }
  offs += fprintf(gl2ps->stream,">>\n");
  return offs;
}

/* Font names */

inline int tools_gl2psPDFgroupListWriteFontResources(tools_GL2PScontext* gl2ps)
{
  int i;
  tools_GL2PSpdfgroup *gro;
  int offs = 0;

  offs += fprintf(gl2ps->stream, "/Font\n<<\n");

  for(i = 0; i < tools_gl2psListNbr(gl2ps->pdfgrouplist); ++i){
    gro = (tools_GL2PSpdfgroup*)tools_gl2psListPointer(gl2ps->pdfgrouplist, i);
    if(gro->fontno < 0)
      continue;
    gro->fontobjno = gl2ps->objects_stack++;
    offs += fprintf(gl2ps->stream, "/F%d %d 0 R\n", gro->fontno, gro->fontobjno);
  }
  offs += fprintf(gl2ps->stream, ">>\n");

  return offs;
}

inline void tools_gl2psPDFgroupListDelete(tools_GL2PScontext* gl2ps)
{
  int i;
  tools_GL2PSpdfgroup *gro = NULL;

  if(!gl2ps->pdfgrouplist)
    return;

  for(i = 0; i < tools_gl2psListNbr(gl2ps->pdfgrouplist); ++i){
    gro = (tools_GL2PSpdfgroup*)tools_gl2psListPointer(gl2ps->pdfgrouplist,i);
    tools_gl2psListDelete(gro->ptrlist);
  }

  tools_gl2psListDelete(gl2ps->pdfgrouplist);
  gl2ps->pdfgrouplist = NULL;
}

/* Print 1st PDF object - file info */

inline int tools_gl2psPrintPDFInfo(tools_GL2PScontext* gl2ps)
{
  int offs;
  time_t now;
  struct tm *newtime;

  time(&now);
  newtime = gmtime(&now);

  offs = fprintf(gl2ps->stream,
                 "1 0 obj\n"
                 "<<\n"
                 "/Title (%s)\n"
                 "/Creator (GL2PS %d.%d.%d%s, %s)\n"
                 "/Producer (%s)\n",
                 gl2ps->title, TOOLS_GL2PS_MAJOR_VERSION, TOOLS_GL2PS_MINOR_VERSION,
                 TOOLS_GL2PS_PATCH_VERSION, TOOLS_GL2PS_EXTRA_VERSION, TOOLS_GL2PS_COPYRIGHT,
                 gl2ps->producer);

  if(!newtime){
    offs += fprintf(gl2ps->stream,
                    ">>\n"
                    "endobj\n");
    return offs;
  }

  offs += fprintf(gl2ps->stream,
                  "/CreationDate (D:%d%02d%02d%02d%02d%02d)\n"
                  ">>\n"
                  "endobj\n",
                  newtime->tm_year+1900,
                  newtime->tm_mon+1,
                  newtime->tm_mday,
                  newtime->tm_hour,
                  newtime->tm_min,
                  newtime->tm_sec);
  return offs;
}

/* Create catalog and page structure - 2nd and 3th PDF object */

inline int tools_gl2psPrintPDFCatalog(tools_GL2PScontext* gl2ps)
{
  return fprintf(gl2ps->stream,
                 "2 0 obj\n"
                 "<<\n"
                 "/Type /Catalog\n"
                 "/Pages 3 0 R\n"
                 ">>\n"
                 "endobj\n");
}

inline int tools_gl2psPrintPDFPages(tools_GL2PScontext* gl2ps)
{
  return fprintf(gl2ps->stream,
                 "3 0 obj\n"
                 "<<\n"
                 "/Type /Pages\n"
                 "/Kids [6 0 R]\n"
                 "/Count 1\n"
                 ">>\n"
                 "endobj\n");
}

/* Open stream for data - graphical objects, fonts etc. PDF object 4 */

inline int tools_gl2psOpenPDFDataStream(tools_GL2PScontext* gl2ps)
{
  int offs = 0;

  offs += fprintf(gl2ps->stream,
                  "4 0 obj\n"
                  "<<\n"
                  "/Length 5 0 R\n" );
  offs += tools_gl2psPrintPDFCompressorType(gl2ps);
  offs += fprintf(gl2ps->stream,
                  ">>\n"
                  "stream\n");
  return offs;
}

/* Stream setup - Graphics state, fill background if allowed */

inline int tools_gl2psOpenPDFDataStreamWritePreface(tools_GL2PScontext* gl2ps)
{
  int offs;

  offs = tools_gl2psPrintf(gl2ps,"/GSa gs\n");

  if(gl2ps->options & TOOLS_GL2PS_DRAW_BACKGROUND){
    offs += tools_gl2psPrintPDFFillColor(gl2ps, gl2ps->bgcolor);
    offs += tools_gl2psPrintf(gl2ps,"%d %d %d %d re\n",
                        (int)gl2ps->viewport[0], (int)gl2ps->viewport[1],
                        (int)gl2ps->viewport[2], (int)gl2ps->viewport[3]);
    offs += tools_gl2psPrintf(gl2ps,"f\n");
  }
  return offs;
}

/* Use the functions above to create the first part of the PDF*/

inline void tools_gl2psPrintPDFHeader(tools_GL2PScontext* gl2ps)
{
  int offs = 0;
  gl2ps->pdfprimlist = tools_gl2psListCreate(500, 500, sizeof(tools_GL2PSprimitive*));
  tools_gl2psPDFstacksInit(gl2ps);

  gl2ps->xreflist = (int*)tools_gl2psMalloc(sizeof(int) * gl2ps->objects_stack);

#if defined(TOOLS_GL2PS_HAVE_ZLIB)
  if(gl2ps->options & TOOLS_GL2PS_COMPRESS){
    tools_gl2psSetupCompress(gl2ps);
  }
#endif
  gl2ps->xreflist[0] = 0;
  offs += fprintf(gl2ps->stream, "%%PDF-1.4\n");
  gl2ps->xreflist[1] = offs;

  offs += tools_gl2psPrintPDFInfo(gl2ps);
  gl2ps->xreflist[2] = offs;

  offs += tools_gl2psPrintPDFCatalog(gl2ps);
  gl2ps->xreflist[3] = offs;

  offs += tools_gl2psPrintPDFPages(gl2ps);
  gl2ps->xreflist[4] = offs;

  offs += tools_gl2psOpenPDFDataStream(gl2ps);
  gl2ps->xreflist[5] = offs; /* finished in tools_gl2psPrintPDFFooter */
  gl2ps->streamlength = tools_gl2psOpenPDFDataStreamWritePreface(gl2ps);
}

/* The central primitive drawing */

inline void tools_gl2psPrintPDFPrimitive(tools_GL2PScontext* gl2ps, void *data)
{
  tools_GL2PSprimitive *prim = *(tools_GL2PSprimitive**)data;

  if((gl2ps->options & TOOLS_GL2PS_OCCLUSION_CULL) && prim->culled)
    return;

  prim = tools_gl2psCopyPrimitive(prim); /* deep copy */
  tools_gl2psListAdd(gl2ps->pdfprimlist, &prim);
}

/* close stream and ... */

inline int tools_gl2psClosePDFDataStream(tools_GL2PScontext* gl2ps)
{
  int offs = 0;

#if defined(TOOLS_GL2PS_HAVE_ZLIB)
  if(gl2ps->options & TOOLS_GL2PS_COMPRESS){
    if(Z_OK != tools_gl2psDeflate(gl2ps))
      tools_gl2psMsg(TOOLS_GL2PS_ERROR, "Zlib deflate error");
    else
      fwrite(gl2ps->compress->dest, gl2ps->compress->destLen, 1, gl2ps->stream);
    gl2ps->streamlength += gl2ps->compress->destLen;

    offs += gl2ps->streamlength;
    tools_gl2psFreeCompress(gl2ps);
  }
#endif

  offs += fprintf(gl2ps->stream,
                  "endstream\n"
                  "endobj\n");
  return offs;
}

/* ... write the now known length object */

inline int tools_gl2psPrintPDFDataStreamLength(tools_GL2PScontext* gl2ps, int val)
{
  return fprintf(gl2ps->stream,
                 "5 0 obj\n"
                 "%d\n"
                 "endobj\n", val);
}

/* Put the info created before in PDF objects */

inline int tools_gl2psPrintPDFOpenPage(tools_GL2PScontext* gl2ps)
{
  int offs;

  /* Write fixed part */

  offs = fprintf(gl2ps->stream,
                 "6 0 obj\n"
                 "<<\n"
                 "/Type /Page\n"
                 "/Parent 3 0 R\n"
                 "/MediaBox [%d %d %d %d]\n",
                 (int)gl2ps->viewport[0], (int)gl2ps->viewport[1],
                 (int)gl2ps->viewport[2], (int)gl2ps->viewport[3]);

  if(gl2ps->options & TOOLS_GL2PS_LANDSCAPE)
    offs += fprintf(gl2ps->stream, "/Rotate -90\n");

  offs += fprintf(gl2ps->stream,
                  "/Contents 4 0 R\n"
                  "/Resources\n"
                  "<<\n"
                  "/ProcSet [/PDF /Text /ImageB /ImageC]  %%/ImageI\n");

  return offs;

  /* End fixed part, proceeds in tools_gl2psPDFgroupListWriteVariableResources() */
}

inline int tools_gl2psPDFgroupListWriteVariableResources(tools_GL2PScontext* gl2ps)
{
  int offs = 0;

  /* a) Graphics States for shader alpha masks*/
  offs += tools_gl2psPDFgroupListWriteGStateResources(gl2ps);

  /* b) Shader and shader masks */
  offs += tools_gl2psPDFgroupListWriteShaderResources(gl2ps);

  /* c) XObjects (Images & Shader Masks) */
  offs += tools_gl2psPDFgroupListWriteXObjectResources(gl2ps);

  /* d) Fonts */
  offs += tools_gl2psPDFgroupListWriteFontResources(gl2ps);

  /* End resources and page */
  offs += fprintf(gl2ps->stream,
                  ">>\n"
                  ">>\n"
                  "endobj\n");
  return offs;
}

/* Standard Graphics State */

inline int tools_gl2psPrintPDFGSObject(tools_GL2PScontext* gl2ps)
{
  return fprintf(gl2ps->stream,
                 "7 0 obj\n"
                 "<<\n"
                 "/Type /ExtGState\n"
                 "/SA false\n"
                 "/SM 0.02\n"
                 "/OP false\n"
                 "/op false\n"
                 "/OPM 0\n"
                 "/BG2 /Default\n"
                 "/UCR2 /Default\n"
                 "/TR2 /Default\n"
                 ">>\n"
                 "endobj\n");
}

/* Put vertex' edge flag (8bit) and coordinates (32bit) in shader stream */

inline int tools_gl2psPrintPDFShaderStreamDataCoord(tools_GL2PScontext* gl2ps, tools_GL2PSvertex *vertex,
                                              int (*action)(tools_GL2PScontext*, unsigned long data, int size),
                                              tools_GLfloat dx, tools_GLfloat dy,
                                              tools_GLfloat xmin, tools_GLfloat ymin)
{
  int offs = 0;
  unsigned long imap;
  tools_GLfloat diff;
//double dmax = ~1UL;
  double dmax = (double)~1UL; //G.Barrand : clang10 : cast.
  char edgeflag = 0;

  /* FIXME: temp bux fix for 64 bit archs: */
  if(sizeof(unsigned long) == 8) dmax = dmax - 2048.;

  offs += (*action)(gl2ps, edgeflag, 1);

  /* The Shader stream in PDF requires to be in a 'big-endian'
     order */

  if(TOOLS_GL2PS_ZERO(dx * dy)){
    offs += (*action)(gl2ps, 0, 4);
    offs += (*action)(gl2ps, 0, 4);
  }
  else{
    diff = (vertex->xyz[0] - xmin) / dx;
    if(diff > 1)
      diff = 1.0F;
    else if(diff < 0)
      diff = 0.0F;
    imap = (unsigned long)(diff * dmax);
    offs += (*action)(gl2ps, imap, 4);

    diff = (vertex->xyz[1] - ymin) / dy;
    if(diff > 1)
      diff = 1.0F;
    else if(diff < 0)
      diff = 0.0F;
    imap = (unsigned long)(diff * dmax);
    offs += (*action)(gl2ps, imap, 4);
  }

  return offs;
}

/* Put vertex' rgb value (8bit for every component) in shader stream */

inline int tools_gl2psPrintPDFShaderStreamDataRGB(tools_GL2PScontext* gl2ps, tools_GL2PSvertex *vertex,
                                            int (*action)(tools_GL2PScontext*, unsigned long data, int size))
{
  int offs = 0;
  unsigned long imap;
//double dmax = ~1UL;
  double dmax = (double)~1UL; //G.Barrand : clang10 : cast.

  /* FIXME: temp bux fix for 64 bit archs: */
  if(sizeof(unsigned long) == 8) dmax = dmax - 2048.;

  imap = (unsigned long)((vertex->rgba[0]) * dmax);
  offs += (*action)(gl2ps, imap, 1);

  imap = (unsigned long)((vertex->rgba[1]) * dmax);
  offs += (*action)(gl2ps, imap, 1);

  imap = (unsigned long)((vertex->rgba[2]) * dmax);
  offs += (*action)(gl2ps, imap, 1);

  return offs;
}

/* Put vertex' alpha (8/16bit) in shader stream */

inline int tools_gl2psPrintPDFShaderStreamDataAlpha(tools_GL2PScontext* gl2ps, tools_GL2PSvertex *vertex,
                                              int (*action)(tools_GL2PScontext*, unsigned long data, int size),
                                              int sigbyte)
{
  int offs = 0;
  unsigned long imap;
//double dmax = ~1UL;
  double dmax = (double)~1UL; //G.Barrand : clang10 : cast.

  /* FIXME: temp bux fix for 64 bit archs: */
  if(sizeof(unsigned long) == 8) dmax = dmax - 2048.;

  if(sigbyte != 8 && sigbyte != 16)
    sigbyte = 8;

  sigbyte /= 8;

  imap = (unsigned long)((vertex->rgba[3]) * dmax);

  offs += (*action)(gl2ps, imap, sigbyte);

  return offs;
}

/* Put a triangles raw data in shader stream */

inline int tools_gl2psPrintPDFShaderStreamData(tools_GL2PScontext* gl2ps, tools_GL2PStriangle *triangle,
                                         tools_GLfloat dx, tools_GLfloat dy,
                                         tools_GLfloat xmin, tools_GLfloat ymin,
                                         int (*action)(tools_GL2PScontext*, unsigned long data, int size),
                                         int a_gray)
{
  int i, offs = 0;
  tools_GL2PSvertex v;

  if(a_gray && a_gray != 8 && a_gray != 16)
    a_gray = 8;

  for(i = 0; i < 3; ++i){
    offs += tools_gl2psPrintPDFShaderStreamDataCoord(gl2ps, &triangle->vertex[i], action,
                                               dx, dy, xmin, ymin);
    if(a_gray){
      v = triangle->vertex[i];
      offs += tools_gl2psPrintPDFShaderStreamDataAlpha(gl2ps, &v, action, a_gray);
    }
    else{
      offs += tools_gl2psPrintPDFShaderStreamDataRGB(gl2ps, &triangle->vertex[i], action);
    }
  }

  return offs;
}

inline void tools_gl2psPDFRectHull(tools_GLfloat *xmin, tools_GLfloat *xmax,
                             tools_GLfloat *ymin, tools_GLfloat *ymax,
                             tools_GL2PStriangle *triangles, int cnt)
{
  int i, j;

  *xmin = triangles[0].vertex[0].xyz[0];
  *xmax = triangles[0].vertex[0].xyz[0];
  *ymin = triangles[0].vertex[0].xyz[1];
  *ymax = triangles[0].vertex[0].xyz[1];

  for(i = 0; i < cnt; ++i){
    for(j = 0; j < 3; ++j){
      if(*xmin > triangles[i].vertex[j].xyz[0])
        *xmin = triangles[i].vertex[j].xyz[0];
      if(*xmax < triangles[i].vertex[j].xyz[0])
        *xmax = triangles[i].vertex[j].xyz[0];
      if(*ymin > triangles[i].vertex[j].xyz[1])
        *ymin = triangles[i].vertex[j].xyz[1];
      if(*ymax < triangles[i].vertex[j].xyz[1])
        *ymax = triangles[i].vertex[j].xyz[1];
    }
  }
}

/* Writes shaded triangle
   gray == 0 means write RGB triangles
   gray == 8             8bit-grayscale (for alpha masks)
   gray == 16            16bit-grayscale (for alpha masks) */

inline int tools_gl2psPrintPDFShader(tools_GL2PScontext* gl2ps, int obj, tools_GL2PStriangle *triangles,
                               int size, int a_gray)
{
  int i, offs = 0, vertexbytes, done = 0;
  tools_GLfloat xmin, xmax, ymin, ymax;

  switch(a_gray){
  case 0:
    vertexbytes = 1+4+4+1+1+1;
    break;
  case 8:
    vertexbytes = 1+4+4+1;
    break;
  case 16:
    vertexbytes = 1+4+4+2;
    break;
  default:
    a_gray = 8;
    vertexbytes = 1+4+4+1;
    break;
  }

  tools_gl2psPDFRectHull(&xmin, &xmax, &ymin, &ymax, triangles, size);

  offs += fprintf(gl2ps->stream,
                  "%d 0 obj\n"
                  "<< "
                  "/ShadingType 4 "
                  "/ColorSpace %s "
                  "/BitsPerCoordinate 32 "
                  "/BitsPerComponent %d "
                  "/BitsPerFlag 8 "
                  "/Decode [%f %f %f %f 0 1 %s] ",
                  obj,
                  (a_gray) ? "/DeviceGray" : "/DeviceRGB",
                  (a_gray) ? a_gray : 8,
                  xmin, xmax, ymin, ymax,
                  (a_gray) ? "" : "0 1 0 1");

#if defined(TOOLS_GL2PS_HAVE_ZLIB)
  if(gl2ps->options & TOOLS_GL2PS_COMPRESS){
    tools_gl2psAllocCompress(gl2ps,vertexbytes * size * 3);

    for(i = 0; i < size; ++i)
      tools_gl2psPrintPDFShaderStreamData(gl2ps,&triangles[i],
                                    xmax-xmin, ymax-ymin, xmin, ymin,
                                    tools_gl2psWriteBigEndianCompress, a_gray);

    if(Z_OK == tools_gl2psDeflate(gl2ps) && 23 + gl2ps->compress->destLen < gl2ps->compress->srcLen){
      offs += tools_gl2psPrintPDFCompressorType(gl2ps);
      offs += fprintf(gl2ps->stream,
                      "/Length %d "
                      ">>\n"
                      "stream\n",
                      (int)gl2ps->compress->destLen);
      offs += gl2ps->compress->destLen * fwrite(gl2ps->compress->dest,
                                                gl2ps->compress->destLen,
                                                1, gl2ps->stream);
      done = 1;
    }
    tools_gl2psFreeCompress(gl2ps);
  }
#endif

  if(!done){
    /* no compression, or too long after compression, or compress error
       -> write non-compressed entry */
    offs += fprintf(gl2ps->stream,
                    "/Length %d "
                    ">>\n"
                    "stream\n",
                    vertexbytes * 3 * size);
    for(i = 0; i < size; ++i)
      offs += tools_gl2psPrintPDFShaderStreamData(gl2ps, &triangles[i],
                                            xmax-xmin, ymax-ymin, xmin, ymin,
                                            tools_gl2psWriteBigEndian, a_gray);
  }

  offs += fprintf(gl2ps->stream,
                  "\nendstream\n"
                  "endobj\n");

  return offs;
}

/* Writes a XObject for a shaded triangle mask */

inline int tools_gl2psPrintPDFShaderMask(tools_GL2PScontext* gl2ps, int obj, int childobj)
{
  int offs = 0, len;

  offs += fprintf(gl2ps->stream,
                  "%d 0 obj\n"
                  "<<\n"
                  "/Type /XObject\n"
                  "/Subtype /Form\n"
                  "/BBox [ %d %d %d %d ]\n"
                  "/Group \n<<\n/S /Transparency /CS /DeviceRGB\n"
                  ">>\n",
                  obj,
                  (int)gl2ps->viewport[0], (int)gl2ps->viewport[1],
                  (int)gl2ps->viewport[2], (int)gl2ps->viewport[3]);

  len = (childobj>0)
    ? (int)strlen("/TrSh sh\n") + (int)log10((double)childobj)+1
    : (int)strlen("/TrSh0 sh\n");

  offs += fprintf(gl2ps->stream,
                  "/Length %d\n"
                  ">>\n"
                  "stream\n",
                  len);
  offs += fprintf(gl2ps->stream,
                  "/TrSh%d sh\n",
                  childobj);
  offs += fprintf(gl2ps->stream,
                  "endstream\n"
                  "endobj\n");

  return offs;
}

/* Writes a Extended graphics state for a shaded triangle mask if
   simplealpha ist true the childobj argument is ignored and a /ca
   statement will be written instead */

inline int tools_gl2psPrintPDFShaderExtGS(tools_GL2PScontext* gl2ps, int obj, int childobj)
{
  int offs = 0;

  offs += fprintf(gl2ps->stream,
                  "%d 0 obj\n"
                  "<<\n",
                  obj);

  offs += fprintf(gl2ps->stream,
                  "/SMask << /S /Alpha /G %d 0 R >> ",
                  childobj);

  offs += fprintf(gl2ps->stream,
                  ">>\n"
                  "endobj\n");
  return offs;
}

/* a simple graphics state */

inline int tools_gl2psPrintPDFShaderSimpleExtGS(tools_GL2PScontext* gl2ps, int obj, tools_GLfloat alpha)
{
  int offs = 0;

  offs += fprintf(gl2ps->stream,
                  "%d 0 obj\n"
                  "<<\n"
                  "/ca %g"
                  ">>\n"
                  "endobj\n",
                  obj, alpha);
  return offs;
}

/* Similar groups of functions for pixmaps and text */

inline int tools_gl2psPrintPDFPixmapStreamData(tools_GL2PScontext* gl2ps, tools_GL2PSimage *im,
                                         int (*action)(tools_GL2PScontext*, unsigned long data, int size),
                                         int a_gray)
{
  int x, y, shift;
  tools_GLfloat _r, _g, _b, _a;

  if(im->format != TOOLS_GL_RGBA && a_gray)
    return 0;

  if(a_gray && a_gray != 8 && a_gray != 16)
    a_gray = 8;

  a_gray /= 8;

  shift = (sizeof(unsigned long) - 1) * 8;

  for(y = 0; y < im->height; ++y){
    for(x = 0; x < im->width; ++x){
      _a = tools_gl2psGetRGB(im, x, y, &_r, &_g, &_b);
      if(im->format == TOOLS_GL_RGBA && a_gray){
        (*action)(gl2ps, (unsigned long)(_a * 255) << shift, a_gray);
      }
      else{
        (*action)(gl2ps, (unsigned long)(_r * 255) << shift, 1);
        (*action)(gl2ps, (unsigned long)(_g * 255) << shift, 1);
        (*action)(gl2ps, (unsigned long)(_b * 255) << shift, 1);
      }
    }
  }

  switch(a_gray){
  case 0: return 3 * im->width * im->height;
  case 1: return im->width * im->height;
  case 2: return 2 * im->width * im->height;
  default: return 3 * im->width * im->height;
  }
}

inline int tools_gl2psPrintPDFPixmap(tools_GL2PScontext* gl2ps, int obj, int childobj, tools_GL2PSimage *im, int a_gray)
{
  int offs = 0, done = 0, sigbytes = 3;

  if(a_gray && a_gray !=8 && a_gray != 16)
    a_gray = 8;

  if(a_gray)
    sigbytes = a_gray / 8;

  offs += fprintf(gl2ps->stream,
                  "%d 0 obj\n"
                  "<<\n"
                  "/Type /XObject\n"
                  "/Subtype /Image\n"
                  "/Width %d\n"
                  "/Height %d\n"
                  "/ColorSpace %s \n"
                  "/BitsPerComponent 8\n",
                  obj,
                  (int)im->width, (int)im->height,
                  (a_gray) ? "/DeviceGray" : "/DeviceRGB" );
  if(TOOLS_GL_RGBA == im->format && a_gray == 0){
    offs += fprintf(gl2ps->stream,
                    "/SMask %d 0 R\n",
                    childobj);
  }

#if defined(TOOLS_GL2PS_HAVE_ZLIB)
  if(gl2ps->options & TOOLS_GL2PS_COMPRESS){
    tools_gl2psAllocCompress(gl2ps,(int)(im->width * im->height * sigbytes));

    tools_gl2psPrintPDFPixmapStreamData(gl2ps, im, tools_gl2psWriteBigEndianCompress, a_gray);

    if(Z_OK == tools_gl2psDeflate(gl2ps) && 23 + gl2ps->compress->destLen < gl2ps->compress->srcLen){
      offs += tools_gl2psPrintPDFCompressorType(gl2ps);
      offs += fprintf(gl2ps->stream,
                      "/Length %d "
                      ">>\n"
                      "stream\n",
                      (int)gl2ps->compress->destLen);
      offs += gl2ps->compress->destLen * fwrite(gl2ps->compress->dest, gl2ps->compress->destLen,
                                                1, gl2ps->stream);
      done = 1;
    }
    tools_gl2psFreeCompress(gl2ps);
  }
#endif

  if(!done){
    /* no compression, or too long after compression, or compress error
       -> write non-compressed entry */
    offs += fprintf(gl2ps->stream,
                    "/Length %d "
                    ">>\n"
                    "stream\n",
                    (int)(im->width * im->height * sigbytes));
    offs += tools_gl2psPrintPDFPixmapStreamData(gl2ps, im, tools_gl2psWriteBigEndian, a_gray);
  }

  offs += fprintf(gl2ps->stream,
                  "\nendstream\n"
                  "endobj\n");

  return offs;
}

inline int tools_gl2psPrintPDFText(tools_GL2PScontext* gl2ps, int obj, tools_GL2PSstring *a_s, int fontnumber)
{
  int offs = 0;

  offs += fprintf(gl2ps->stream,
                  "%d 0 obj\n"
                  "<<\n"
                  "/Type /Font\n"
                  "/Subtype /Type1\n"
                  "/Name /F%d\n"
                  "/BaseFont /%s\n"
                  "/Encoding /MacRomanEncoding\n"
                  ">>\n"
                  "endobj\n",
                  obj, fontnumber, a_s->fontname);
  return offs;
}

/* Write the physical objects */

inline int tools_gl2psPDFgroupListWriteObjects(tools_GL2PScontext* gl2ps, int entryoffs)
{
  int i,j;
  tools_GL2PSprimitive *p = NULL;
  tools_GL2PSpdfgroup *gro;
  int offs = entryoffs;
  tools_GL2PStriangle *triangles;
  int size = 0;

  if(!gl2ps->pdfgrouplist)
    return offs;

  for(i = 0; i < tools_gl2psListNbr(gl2ps->pdfgrouplist); ++i){
    gro = (tools_GL2PSpdfgroup*)tools_gl2psListPointer(gl2ps->pdfgrouplist, i);
    if(!tools_gl2psListNbr(gro->ptrlist))
      continue;
    p = *(tools_GL2PSprimitive**)tools_gl2psListPointer(gro->ptrlist, 0);
    switch(p->type){
    case TOOLS_GL2PS_POINT:
      break;
    case TOOLS_GL2PS_LINE:
      break;
    case TOOLS_GL2PS_TRIANGLE:
      size = tools_gl2psListNbr(gro->ptrlist);
      triangles = (tools_GL2PStriangle*)tools_gl2psMalloc(sizeof(tools_GL2PStriangle) * size);
      for(j = 0; j < size; ++j){
        p = *(tools_GL2PSprimitive**)tools_gl2psListPointer(gro->ptrlist, j);
        tools_gl2psFillTriangleFromPrimitive(&triangles[j], p, TOOLS_GL_TRUE);
      }
      if(triangles[0].prop & T_VAR_COLOR){
        gl2ps->xreflist[gro->shobjno] = offs;
        offs += tools_gl2psPrintPDFShader(gl2ps, gro->shobjno, triangles, size, 0);
      }
      if(triangles[0].prop & T_ALPHA_LESS_1){
        gl2ps->xreflist[gro->gsobjno] = offs;
        offs += tools_gl2psPrintPDFShaderSimpleExtGS(gl2ps, gro->gsobjno, triangles[0].vertex[0].rgba[3]);
      }
      if(triangles[0].prop & T_VAR_ALPHA){
        gl2ps->xreflist[gro->gsobjno] = offs;
        offs += tools_gl2psPrintPDFShaderExtGS(gl2ps, gro->gsobjno, gro->trgroupobjno);
        gl2ps->xreflist[gro->trgroupobjno] = offs;
        offs += tools_gl2psPrintPDFShaderMask(gl2ps, gro->trgroupobjno, gro->maskshno);
        gl2ps->xreflist[gro->maskshobjno] = offs;
        offs += tools_gl2psPrintPDFShader(gl2ps, gro->maskshobjno, triangles, size, 8);
      }
      tools_gl2psFree(triangles);
      break;
    case TOOLS_GL2PS_PIXMAP:
      gl2ps->xreflist[gro->imobjno] = offs;
      offs += tools_gl2psPrintPDFPixmap(gl2ps, gro->imobjno, gro->imobjno+1, p->data.image, 0);
      if(p->data.image->format == TOOLS_GL_RGBA){
        gl2ps->xreflist[gro->imobjno+1] = offs;
        offs += tools_gl2psPrintPDFPixmap(gl2ps, gro->imobjno+1, -1, p->data.image, 8);
      }
      break;
    case TOOLS_GL2PS_TEXT:
      gl2ps->xreflist[gro->fontobjno] = offs;
      offs += tools_gl2psPrintPDFText(gl2ps, gro->fontobjno,p->data.text,gro->fontno);
      break;
    case TOOLS_GL2PS_SPECIAL :
      /* alignment contains the format for which the special output text
         is intended */
      if(p->data.text->alignment == TOOLS_GL2PS_PDF)
        offs += fprintf(gl2ps->stream, "%s\n", p->data.text->str);
      break;
    default:
      break;
    }
  }
  return offs;
}

/* All variable data has been written at this point and all required
   functioninality has been gathered, so we can write now file footer
   with cross reference table and trailer */

inline void tools_gl2psPrintPDFFooter(tools_GL2PScontext* gl2ps)
{
  int i, offs;

  tools_gl2psPDFgroupListInit(gl2ps);
  tools_gl2psPDFgroupListWriteMainStream(gl2ps);

  offs = gl2ps->xreflist[5] + gl2ps->streamlength;
  offs += tools_gl2psClosePDFDataStream(gl2ps);
  gl2ps->xreflist[5] = offs;

  offs += tools_gl2psPrintPDFDataStreamLength(gl2ps, gl2ps->streamlength);
  gl2ps->xreflist[6] = offs;
  gl2ps->streamlength = 0;

  offs += tools_gl2psPrintPDFOpenPage(gl2ps);
  offs += tools_gl2psPDFgroupListWriteVariableResources(gl2ps);
  gl2ps->xreflist = (int*)tools_gl2psRealloc(gl2ps->xreflist,
                                       sizeof(int) * (gl2ps->objects_stack + 1));
  gl2ps->xreflist[7] = offs;

  offs += tools_gl2psPrintPDFGSObject(gl2ps);
  gl2ps->xreflist[8] = offs;

  gl2ps->xreflist[gl2ps->objects_stack] =
    tools_gl2psPDFgroupListWriteObjects(gl2ps, gl2ps->xreflist[8]);

  /* Start cross reference table. The file has to been opened in
     binary mode to preserve the 20 digit string length! */
  fprintf(gl2ps->stream,
          "xref\n"
          "0 %d\n"
          "%010d 65535 f \n", gl2ps->objects_stack, 0);

  for(i = 1; i < gl2ps->objects_stack; ++i)
    fprintf(gl2ps->stream, "%010d 00000 n \n", gl2ps->xreflist[i]);

  fprintf(gl2ps->stream,
          "trailer\n"
          "<<\n"
          "/Size %d\n"
          "/Info 1 0 R\n"
          "/Root 2 0 R\n"
          ">>\n"
          "startxref\n%d\n"
          "%%%%EOF\n",
          gl2ps->objects_stack, gl2ps->xreflist[gl2ps->objects_stack]);

  /* Free auxiliary lists and arrays */
  tools_gl2psFree(gl2ps->xreflist);
  tools_gl2psListAction(gl2ps->pdfprimlist, tools_gl2psFreePrimitive);
  tools_gl2psListDelete(gl2ps->pdfprimlist);
  tools_gl2psPDFgroupListDelete(gl2ps);

#if defined(TOOLS_GL2PS_HAVE_ZLIB)
  if(gl2ps->options & TOOLS_GL2PS_COMPRESS){
    tools_gl2psFreeCompress(gl2ps);
    tools_gl2psFree(gl2ps->compress);
    gl2ps->compress = NULL;
  }
#endif
}

/* PDF begin viewport */

inline void tools_gl2psPrintPDFBeginViewport(tools_GL2PScontext* gl2ps, tools_GLint viewport[4])
{
  int offs = 0;
  tools_GLint idx;
  tools_GLfloat rgba[4];
  int x = viewport[0], y = viewport[1], w = viewport[2], h = viewport[3];

  tools_glRenderMode(TOOLS_GL_FEEDBACK);

  tools_gl2psResetLineProperties(gl2ps);

  if(gl2ps->header){
    tools_gl2psPrintPDFHeader(gl2ps);
    gl2ps->header = TOOLS_GL_FALSE;
  }

  offs += tools_gl2psPrintf(gl2ps,"q\n");

  if(gl2ps->options & TOOLS_GL2PS_DRAW_BACKGROUND){
    if(gl2ps->colormode == TOOLS_GL_RGBA || gl2ps->colorsize == 0){
      tools_glGetFloatv(TOOLS_GL_COLOR_CLEAR_VALUE, rgba);
    }
    else{
      tools_glGetIntegerv(TOOLS_GL_INDEX_CLEAR_VALUE, &idx);
      rgba[0] = gl2ps->colormap[idx][0];
      rgba[1] = gl2ps->colormap[idx][1];
      rgba[2] = gl2ps->colormap[idx][2];
      rgba[3] = 1.0F;
    }
    offs += tools_gl2psPrintPDFFillColor(gl2ps, rgba);
    offs += tools_gl2psPrintf(gl2ps,"%d %d %d %d re\n"
                        "W\n"
                        "f\n",
                        x, y, w, h);
  }
  else{
    offs += tools_gl2psPrintf(gl2ps,"%d %d %d %d re\n"
                        "W\n"
                        "n\n",
                        x, y, w, h);
  }

  gl2ps->streamlength += offs;
}

inline tools_GLint tools_gl2psPrintPDFEndViewport(tools_GL2PScontext* gl2ps)
{
  tools_GLint res;

  res = tools_gl2psPrintPrimitives(gl2ps);
  gl2ps->streamlength += tools_gl2psPrintf(gl2ps,"Q\n");
  return res;
}

inline void tools_gl2psPrintPDFFinalPrimitive(tools_GL2PScontext*)
{
}

/* definition of the PDF backend */

static const tools_GL2PSbackend tools_gl2psPDF = {
  tools_gl2psPrintPDFHeader,
  tools_gl2psPrintPDFFooter,
  tools_gl2psPrintPDFBeginViewport,
  tools_gl2psPrintPDFEndViewport,
  tools_gl2psPrintPDFPrimitive,
  tools_gl2psPrintPDFFinalPrimitive,
  "pdf",
  "Portable Document Format"
};

/*********************************************************************
 *
 * SVG routines
 *
 *********************************************************************/

inline void tools_gl2psSVGGetCoordsAndColors(tools_GL2PScontext* gl2ps, int n, tools_GL2PSvertex *verts,
                                       tools_GL2PSxyz *xyz, tools_GL2PSrgba *rgba)
{
  int i, j;

  for(i = 0; i < n; i++){
    xyz[i][0] = verts[i].xyz[0];
    xyz[i][1] = gl2ps->viewport[3] - verts[i].xyz[1];
    xyz[i][2] = 0.0F;
    for(j = 0; j < 4; j++)
      rgba[i][j] = verts[i].rgba[j];
  }
}

#include <sstream>  //G.Barrand
#include <iomanip>  //G.Barrand

inline void tools_gl2psSVGGetColorString(tools_GL2PSrgba rgba, char str[32])
{
  int _r = (int)(255. * rgba[0]);
  int _g = (int)(255. * rgba[1]);
  int _b = (int)(255. * rgba[2]);
  int rc = (_r < 0) ? 0 : (_r > 255) ? 255 : _r;
  int gc = (_g < 0) ? 0 : (_g > 255) ? 255 : _g;
  int bc = (_b < 0) ? 0 : (_b > 255) ? 255 : _b;
//sprintf(str, "#%2.2x%2.2x%2.2x", rc, gc, bc); //G.Barrand
//G.Barrand:begin:
  std::ostringstream oss;
  oss << "#";
  oss << std::setw(2) << std::setfill('0') << std::hex << rc;
  oss << std::setw(2) << std::setfill('0') << std::hex << gc;
  oss << std::setw(2) << std::setfill('0') << std::hex << bc;
  strcpy(str,oss.str().c_str());
//G.Barrand:end.
}

inline void tools_gl2psPrintSVGHeader(tools_GL2PScontext* gl2ps)
{
  int x, y, width, height;
  char col[32];
  time_t now;

  time(&now);

  if (gl2ps->options & TOOLS_GL2PS_LANDSCAPE){
    x = (int)gl2ps->viewport[1];
    y = (int)gl2ps->viewport[0];
    width = (int)gl2ps->viewport[3];
    height = (int)gl2ps->viewport[2];
  }
  else{
    x = (int)gl2ps->viewport[0];
    y = (int)gl2ps->viewport[1];
    width = (int)gl2ps->viewport[2];
    height = (int)gl2ps->viewport[3];
  }

  /* Compressed SVG files (.svgz) are simply gzipped SVG files */
  tools_gl2psPrintGzipHeader(gl2ps);

  tools_gl2psPrintf(gl2ps,"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n");
  tools_gl2psPrintf(gl2ps,"<svg xmlns=\"http://www.w3.org/2000/svg\"\n");
  tools_gl2psPrintf(gl2ps,"     xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n"
              "     width=\"%dpt\" height=\"%dpt\" viewBox=\"%d %d %d %d\">\n",
              width, height, x, y, width, height);
  tools_gl2psPrintf(gl2ps,"<title>%s</title>\n", gl2ps->title);
  tools_gl2psPrintf(gl2ps,"<desc>\n");
  tools_gl2psPrintf(gl2ps,"Creator: GL2PS %d.%d.%d%s, %s\n"
              "For: %s\n"
              "CreationDate: %s",
              TOOLS_GL2PS_MAJOR_VERSION, TOOLS_GL2PS_MINOR_VERSION, TOOLS_GL2PS_PATCH_VERSION,
              TOOLS_GL2PS_EXTRA_VERSION, TOOLS_GL2PS_COPYRIGHT, gl2ps->producer, ctime(&now));
  tools_gl2psPrintf(gl2ps,"</desc>\n");
  tools_gl2psPrintf(gl2ps,"<defs>\n");
  tools_gl2psPrintf(gl2ps,"</defs>\n");

  if(gl2ps->options & TOOLS_GL2PS_DRAW_BACKGROUND){
    tools_gl2psSVGGetColorString(gl2ps->bgcolor, col);
    tools_gl2psPrintf(gl2ps,"<polygon fill=\"%s\" points=\"%d,%d %d,%d %d,%d %d,%d\"/>\n", col,
                (int)gl2ps->viewport[0], (int)gl2ps->viewport[1],
                (int)gl2ps->viewport[2], (int)gl2ps->viewport[1],
                (int)gl2ps->viewport[2], (int)gl2ps->viewport[3],
                (int)gl2ps->viewport[0], (int)gl2ps->viewport[3]);
  }

  /* group all the primitives and disable antialiasing */
  tools_gl2psPrintf(gl2ps,"<g>\n");
}

inline void tools_gl2psPrintSVGSmoothTriangle(tools_GL2PScontext* gl2ps, tools_GL2PSxyz xyz[3], tools_GL2PSrgba rgba[3])
{
  int i;
  tools_GL2PSxyz xyz2[3];
  tools_GL2PSrgba rgba2[3];
  char col[32];

  /* Apparently there is no easy way to do Gouraud shading in SVG
     without explicitly pre-defining gradients, so for now we just do
     recursive subdivision */

  if(tools_gl2psSameColorThreshold(3, rgba, gl2ps->threshold)){
    tools_gl2psSVGGetColorString(rgba[0], col);
    tools_gl2psPrintf(gl2ps,"<polygon fill=\"%s\" ", col);
    if(rgba[0][3] < 1.0F) tools_gl2psPrintf(gl2ps,"fill-opacity=\"%g\" ", rgba[0][3]);
    tools_gl2psPrintf(gl2ps,"shape-rendering=\"crispEdges\" ");
    tools_gl2psPrintf(gl2ps,"points=\"%g,%g %g,%g %g,%g\"/>\n", xyz[0][0], xyz[0][1],
                xyz[1][0], xyz[1][1], xyz[2][0], xyz[2][1]);
  }
  else{
    /* subdivide into 4 subtriangles */
    for(i = 0; i < 3; i++){
      xyz2[0][i] = xyz[0][i];
      xyz2[1][i] = 0.5F * (xyz[0][i] + xyz[1][i]);
      xyz2[2][i] = 0.5F * (xyz[0][i] + xyz[2][i]);
    }
    for(i = 0; i < 4; i++){
      rgba2[0][i] = rgba[0][i];
      rgba2[1][i] = 0.5F * (rgba[0][i] + rgba[1][i]);
      rgba2[2][i] = 0.5F * (rgba[0][i] + rgba[2][i]);
    }
    tools_gl2psPrintSVGSmoothTriangle(gl2ps, xyz2, rgba2);
    for(i = 0; i < 3; i++){
      xyz2[0][i] = 0.5F * (xyz[0][i] + xyz[1][i]);
      xyz2[1][i] = xyz[1][i];
      xyz2[2][i] = 0.5F * (xyz[1][i] + xyz[2][i]);
    }
    for(i = 0; i < 4; i++){
      rgba2[0][i] = 0.5F * (rgba[0][i] + rgba[1][i]);
      rgba2[1][i] = rgba[1][i];
      rgba2[2][i] = 0.5F * (rgba[1][i] + rgba[2][i]);
    }
    tools_gl2psPrintSVGSmoothTriangle(gl2ps, xyz2, rgba2);
    for(i = 0; i < 3; i++){
      xyz2[0][i] = 0.5F * (xyz[0][i] + xyz[2][i]);
      xyz2[1][i] = xyz[2][i];
      xyz2[2][i] = 0.5F * (xyz[1][i] + xyz[2][i]);
    }
    for(i = 0; i < 4; i++){
      rgba2[0][i] = 0.5F * (rgba[0][i] + rgba[2][i]);
      rgba2[1][i] = rgba[2][i];
      rgba2[2][i] = 0.5F * (rgba[1][i] + rgba[2][i]);
    }
    tools_gl2psPrintSVGSmoothTriangle(gl2ps, xyz2, rgba2);
    for(i = 0; i < 3; i++){
      xyz2[0][i] = 0.5F * (xyz[0][i] + xyz[1][i]);
      xyz2[1][i] = 0.5F * (xyz[1][i] + xyz[2][i]);
      xyz2[2][i] = 0.5F * (xyz[0][i] + xyz[2][i]);
    }
    for(i = 0; i < 4; i++){
      rgba2[0][i] = 0.5F * (rgba[0][i] + rgba[1][i]);
      rgba2[1][i] = 0.5F * (rgba[1][i] + rgba[2][i]);
      rgba2[2][i] = 0.5F * (rgba[0][i] + rgba[2][i]);
    }
    tools_gl2psPrintSVGSmoothTriangle(gl2ps, xyz2, rgba2);
  }
}

inline void tools_gl2psPrintSVGDash(tools_GL2PScontext* gl2ps, tools_GLushort pattern, tools_GLint factor)
{
  int i, n, array[10];

  if(!pattern || !factor) return; /* solid line */

  tools_gl2psParseStipplePattern(pattern, factor, &n, array);
  tools_gl2psPrintf(gl2ps,"stroke-dasharray=\"");
  for(i = 0; i < n; i++){
    if(i) tools_gl2psPrintf(gl2ps,",");
    tools_gl2psPrintf(gl2ps,"%d", array[i]);
  }
  tools_gl2psPrintf(gl2ps,"\" ");
}

inline void tools_gl2psEndSVGLine(tools_GL2PScontext* gl2ps)
{
  int i;
  if(gl2ps->lastvertex.rgba[0] >= 0.){
    tools_gl2psPrintf(gl2ps,"%g,%g\"/>\n", gl2ps->lastvertex.xyz[0],
                gl2ps->viewport[3] - gl2ps->lastvertex.xyz[1]);
    for(i = 0; i < 3; i++)
      gl2ps->lastvertex.xyz[i] = -1.;
    for(i = 0; i < 4; i++)
      gl2ps->lastvertex.rgba[i] = -1.;
  }
}

inline void tools_gl2psPrintSVGPixmap(tools_GL2PScontext* gl2ps, tools_GLfloat x, tools_GLfloat y, tools_GL2PSimage *pixmap)
{
#if defined(TOOLS_GL2PS_HAVE_LIBPNG)
  tools_GL2PSlist *png;
  unsigned char c;
  int i;

  /* The only image types supported by the SVG standard are JPEG, PNG
     and SVG. Here we choose PNG, and since we want to embed the image
     directly in the SVG stream (and not link to an external image
     file), we need to encode the pixmap into PNG in memory, then
     encode it into base64. */

  png = tools_gl2psListCreate(pixmap->width * pixmap->height * 3, 1000,
                        sizeof(unsigned char));
  tools_gl2psConvertPixmapToPNG(pixmap, png);
  tools_gl2psListEncodeBase64(png);

  /* Use "transform" attribute to scale and translate the image from
     the coordinates origin (0,0) */
  y -= pixmap->zoom_y * (tools_GLfloat)pixmap->height;
  tools_gl2psPrintf(gl2ps,"<image x=\"%g\" y=\"%g\" width=\"%d\" height=\"%d\"\n",
              0., 0., pixmap->width, pixmap->height);
  tools_gl2psPrintf(gl2ps,"transform=\"matrix(%g,0,0,%g,%g,%g)\"\n",
              pixmap->zoom_x, pixmap->zoom_y, x, y);
  tools_gl2psPrintf(gl2ps,"xlink:href=\"data:image/png;base64,");
  for(i = 0; i < tools_gl2psListNbr(png); i++){
    tools_gl2psListRead(png, i, &c);
    tools_gl2psPrintf(gl2ps,"%c", c);
  }
  tools_gl2psPrintf(gl2ps,"\"/>\n");
  tools_gl2psListDelete(png);
#else
  (void) x; (void) y; (void) pixmap;  /* not used */
  tools_gl2psMsg(TOOLS_GL2PS_WARNING, "GL2PS must be compiled with PNG support in "
           "order to embed images in SVG streams");
#endif
  (void)gl2ps;
}

inline void tools_gl2psPrintSVGPrimitive(tools_GL2PScontext* gl2ps, void *data)
{
  tools_GL2PSprimitive *prim;
  tools_GL2PSxyz xyz[4];
  tools_GL2PSrgba rgba[4];
  char col[32];
  char lcap[7], ljoin[7];
  int newline;

  prim = *(tools_GL2PSprimitive**)data;

  if((gl2ps->options & TOOLS_GL2PS_OCCLUSION_CULL) && prim->culled) return;

  /* We try to draw connected lines as a single path to get nice line
     joins and correct stippling. So if the primitive to print is not
     a line we must first finish the current line (if any): */
  if(prim->type != TOOLS_GL2PS_LINE) tools_gl2psEndSVGLine(gl2ps);

  tools_gl2psSVGGetCoordsAndColors(gl2ps, prim->numverts, prim->verts, xyz, rgba);

  switch(prim->type){
  case TOOLS_GL2PS_POINT :
    tools_gl2psSVGGetColorString(rgba[0], col);
    tools_gl2psPrintf(gl2ps,"<circle fill=\"%s\" ", col);
    if(rgba[0][3] < 1.0F) tools_gl2psPrintf(gl2ps,"fill-opacity=\"%g\" ", rgba[0][3]);
    tools_gl2psPrintf(gl2ps,"cx=\"%g\" cy=\"%g\" r=\"%g\"/>\n",
                xyz[0][0], xyz[0][1], 0.5 * prim->width);
    break;
  case TOOLS_GL2PS_LINE :
    if(!tools_gl2psSamePosition(gl2ps->lastvertex.xyz, prim->verts[0].xyz) ||
       !tools_gl2psSameColor(gl2ps->lastrgba, prim->verts[0].rgba) ||
       gl2ps->lastlinewidth != prim->width ||
       gl2ps->lastlinecap != prim->linecap ||
       gl2ps->lastlinejoin != prim->linejoin ||
       gl2ps->lastpattern != prim->pattern ||
       gl2ps->lastfactor != prim->factor){
      /* End the current line if the new segment does not start where
         the last one ended, or if the color, the width or the
         stippling have changed (we will need to use multi-point
         gradients for smooth-shaded lines) */
      tools_gl2psEndSVGLine(gl2ps);
      newline = 1;
    }
    else{
      newline = 0;
    }
    gl2ps->lastvertex = prim->verts[1];
    tools_gl2psSetLastColor(gl2ps, prim->verts[0].rgba);
    gl2ps->lastlinewidth = prim->width;
    gl2ps->lastlinecap = prim->linecap;
    gl2ps->lastlinejoin = prim->linejoin;
    gl2ps->lastpattern = prim->pattern;
    gl2ps->lastfactor = prim->factor;
    if(newline){
      tools_gl2psSVGGetColorString(rgba[0], col);
      tools_gl2psPrintf(gl2ps,"<polyline fill=\"none\" stroke=\"%s\" stroke-width=\"%g\" ",
                  col, prim->width);
      switch (prim->linecap){
      case TOOLS_GL2PS_LINE_CAP_BUTT:
      //sprintf (lcap, "%s", "butt");  //G.Barrand
        strcpy (lcap, "butt");         //G.Barrand
        break;
      case TOOLS_GL2PS_LINE_CAP_ROUND:
      //sprintf (lcap, "%s", "round"); //G.Barrand
        strcpy (lcap, "round");        //G.Barrand
        break;
      case TOOLS_GL2PS_LINE_CAP_SQUARE:
      //sprintf (lcap, "%s", "square"); //G.Barrand
        strcpy (lcap, "square");        //G.Barrand
        break;
      default: /*G.Barrand : to quiet Coverity :*/
      //sprintf (lcap, "%s", "butt");   //G.Barrand
        strcpy (lcap, "butt");          //G.Barrand
        break;
      }
      switch (prim->linejoin){
      case TOOLS_GL2PS_LINE_JOIN_MITER:
      //sprintf (ljoin, "%s", "miter");  //G.Barrand
        strcpy (ljoin, "miter");         //G.Barrand 
        break;
      case TOOLS_GL2PS_LINE_JOIN_ROUND:
      //sprintf (ljoin, "%s", "round");  //G.Barrand
        strcpy (ljoin, "round");         //G.Barrand
        break;
      case TOOLS_GL2PS_LINE_JOIN_BEVEL:
      //sprintf (ljoin, "%s", "bevel");  //G.Barrand
        strcpy (ljoin, "bevel");         //G.Barrand
        break;
      default: /*G.Barrand : to quiet Coverity :*/
      //sprintf (ljoin, "%s", "miter");  //G.Barrand 
        strcpy (ljoin, "miter");         //G.Barrand
        break;
      }
      tools_gl2psPrintf(gl2ps,"stroke-linecap=\"%s\" stroke-linejoin=\"%s\" ",
                  lcap, ljoin);
      if(rgba[0][3] < 1.0F) tools_gl2psPrintf(gl2ps,"stroke-opacity=\"%g\" ", rgba[0][3]);
      tools_gl2psPrintSVGDash(gl2ps, prim->pattern, prim->factor);
      tools_gl2psPrintf(gl2ps,"points=\"%g,%g ", xyz[0][0], xyz[0][1]);
    }
    else{
      tools_gl2psPrintf(gl2ps,"%g,%g ", xyz[0][0], xyz[0][1]);
    }
    break;
  case TOOLS_GL2PS_TRIANGLE :
    tools_gl2psPrintSVGSmoothTriangle(gl2ps, xyz, rgba);
    break;
  case TOOLS_GL2PS_QUADRANGLE :
    tools_gl2psMsg(TOOLS_GL2PS_WARNING, "There should not be any quad left to print");
    break;
  case TOOLS_GL2PS_PIXMAP :
    tools_gl2psPrintSVGPixmap(gl2ps,xyz[0][0], xyz[0][1], prim->data.image);
    break;
  case TOOLS_GL2PS_TEXT :
    tools_gl2psSVGGetColorString(prim->verts[0].rgba, col);
    tools_gl2psPrintf(gl2ps,"<text fill=\"%s\" x=\"%g\" y=\"%g\" font-size=\"%d\" ",
                col, xyz[0][0], xyz[0][1], prim->data.text->fontsize);
    if(prim->data.text->angle)
      tools_gl2psPrintf(gl2ps,"transform=\"rotate(%g, %g, %g)\" ",
                  -prim->data.text->angle, xyz[0][0], xyz[0][1]);
    switch(prim->data.text->alignment){
    case TOOLS_GL2PS_TEXT_C:
      tools_gl2psPrintf(gl2ps,"text-anchor=\"middle\" dy=\"%d\" ",
                  prim->data.text->fontsize / 2);
      break;
    case TOOLS_GL2PS_TEXT_CL:
      tools_gl2psPrintf(gl2ps,"text-anchor=\"start\" dy=\"%d\" ",
                  prim->data.text->fontsize / 2);
      break;
    case TOOLS_GL2PS_TEXT_CR:
      tools_gl2psPrintf(gl2ps,"text-anchor=\"end\" dy=\"%d\" ",
                  prim->data.text->fontsize / 2);
      break;
    case TOOLS_GL2PS_TEXT_B:
      tools_gl2psPrintf(gl2ps,"text-anchor=\"middle\" dy=\"0\" ");
      break;
    case TOOLS_GL2PS_TEXT_BR:
      tools_gl2psPrintf(gl2ps,"text-anchor=\"end\" dy=\"0\" ");
      break;
    case TOOLS_GL2PS_TEXT_T:
      tools_gl2psPrintf(gl2ps,"text-anchor=\"middle\" dy=\"%d\" ",
                  prim->data.text->fontsize);
      break;
    case TOOLS_GL2PS_TEXT_TL:
      tools_gl2psPrintf(gl2ps,"text-anchor=\"start\" dy=\"%d\" ",
                  prim->data.text->fontsize);
      break;
    case TOOLS_GL2PS_TEXT_TR:
      tools_gl2psPrintf(gl2ps,"text-anchor=\"end\" dy=\"%d\" ",
                  prim->data.text->fontsize);
      break;
    case TOOLS_GL2PS_TEXT_BL:
    default: /* same as TOOLS_GL2PS_TEXT_BL */
      tools_gl2psPrintf(gl2ps,"text-anchor=\"start\" dy=\"0\" ");
      break;
    }
    if(!strcmp(prim->data.text->fontname, "Times-Roman"))
      tools_gl2psPrintf(gl2ps,"font-family=\"Times\">");
    else if(!strcmp(prim->data.text->fontname, "Times-Bold"))
      tools_gl2psPrintf(gl2ps,"font-family=\"Times\" font-weight=\"bold\">");
    else if(!strcmp(prim->data.text->fontname, "Times-Italic"))
      tools_gl2psPrintf(gl2ps,"font-family=\"Times\" font-style=\"italic\">");
    else if(!strcmp(prim->data.text->fontname, "Times-BoldItalic"))
      tools_gl2psPrintf(gl2ps,"font-family=\"Times\" font-style=\"italic\" font-weight=\"bold\">");
    else if(!strcmp(prim->data.text->fontname, "Helvetica-Bold"))
      tools_gl2psPrintf(gl2ps,"font-family=\"Helvetica\" font-weight=\"bold\">");
    else if(!strcmp(prim->data.text->fontname, "Helvetica-Oblique"))
      tools_gl2psPrintf(gl2ps,"font-family=\"Helvetica\" font-style=\"oblique\">");
    else if(!strcmp(prim->data.text->fontname, "Helvetica-BoldOblique"))
      tools_gl2psPrintf(gl2ps,"font-family=\"Helvetica\" font-style=\"oblique\" font-weight=\"bold\">");
    else if(!strcmp(prim->data.text->fontname, "Courier-Bold"))
      tools_gl2psPrintf(gl2ps,"font-family=\"Courier\" font-weight=\"bold\">");
    else if(!strcmp(prim->data.text->fontname, "Courier-Oblique"))
      tools_gl2psPrintf(gl2ps,"font-family=\"Courier\" font-style=\"oblique\">");
    else if(!strcmp(prim->data.text->fontname, "Courier-BoldOblique"))
      tools_gl2psPrintf(gl2ps,"font-family=\"Courier\" font-style=\"oblique\" font-weight=\"bold\">");
    else
      tools_gl2psPrintf(gl2ps,"font-family=\"%s\">", prim->data.text->fontname);
    tools_gl2psPrintf(gl2ps,"%s</text>\n", prim->data.text->str);
    break;
  case TOOLS_GL2PS_SPECIAL :
    /* alignment contains the format for which the special output text
       is intended */
    if(prim->data.text->alignment == TOOLS_GL2PS_SVG)
      tools_gl2psPrintf(gl2ps,"%s\n", prim->data.text->str);
    break;
  default :
    break;
  }
}

inline void tools_gl2psPrintSVGFooter(tools_GL2PScontext* gl2ps)
{
  tools_gl2psPrintf(gl2ps,"</g>\n");
  tools_gl2psPrintf(gl2ps,"</svg>\n");

  tools_gl2psPrintGzipFooter(gl2ps);
}

inline void tools_gl2psPrintSVGBeginViewport(tools_GL2PScontext* gl2ps, tools_GLint viewport[4])
{
  tools_GLint idx;
  char col[32];
  tools_GLfloat rgba[4];
  int x = viewport[0], y = viewport[1], w = viewport[2], h = viewport[3];

  tools_glRenderMode(TOOLS_GL_FEEDBACK);

  tools_gl2psResetLineProperties(gl2ps);

  if(gl2ps->header){
    tools_gl2psPrintSVGHeader(gl2ps);
    gl2ps->header = TOOLS_GL_FALSE;
  }

  if(gl2ps->options & TOOLS_GL2PS_DRAW_BACKGROUND){
    if(gl2ps->colormode == TOOLS_GL_RGBA || gl2ps->colorsize == 0){
      tools_glGetFloatv(TOOLS_GL_COLOR_CLEAR_VALUE, rgba);
    }
    else{
      tools_glGetIntegerv(TOOLS_GL_INDEX_CLEAR_VALUE, &idx);
      rgba[0] = gl2ps->colormap[idx][0];
      rgba[1] = gl2ps->colormap[idx][1];
      rgba[2] = gl2ps->colormap[idx][2];
      rgba[3] = 1.0F;
    }
    tools_gl2psSVGGetColorString(rgba, col);
    tools_gl2psPrintf(gl2ps,"<polygon fill=\"%s\" points=\"%d,%d %d,%d %d,%d %d,%d\" ", col,
                x, gl2ps->viewport[3] - y,
                x + w, gl2ps->viewport[3] - y,
                x + w, gl2ps->viewport[3] - (y + h),
                x, gl2ps->viewport[3] - (y + h));
    tools_gl2psPrintf(gl2ps,"shape-rendering=\"crispEdges\"/>\n");
  }

  tools_gl2psPrintf(gl2ps,"<clipPath id=\"cp%d%d%d%d\">\n", x, y, w, h);
  tools_gl2psPrintf(gl2ps,"  <polygon points=\"%d,%d %d,%d %d,%d %d,%d\"/>\n",
              x, gl2ps->viewport[3] - y,
              x + w, gl2ps->viewport[3] - y,
              x + w, gl2ps->viewport[3] - (y + h),
              x, gl2ps->viewport[3] - (y + h));
  tools_gl2psPrintf(gl2ps,"</clipPath>\n");
  tools_gl2psPrintf(gl2ps,"<g clip-path=\"url(#cp%d%d%d%d)\">\n", x, y, w, h);
}

inline tools_GLint tools_gl2psPrintSVGEndViewport(tools_GL2PScontext* gl2ps)
{
  tools_GLint res;

  res = tools_gl2psPrintPrimitives(gl2ps);
  tools_gl2psPrintf(gl2ps,"</g>\n");
  return res;
}

inline void tools_gl2psPrintSVGFinalPrimitive(tools_GL2PScontext* gl2ps)
{
  /* End any remaining line, if any */
  tools_gl2psEndSVGLine(gl2ps);
}

/* definition of the SVG backend */

static const tools_GL2PSbackend tools_gl2psSVG = {
  tools_gl2psPrintSVGHeader,
  tools_gl2psPrintSVGFooter,
  tools_gl2psPrintSVGBeginViewport,
  tools_gl2psPrintSVGEndViewport,
  tools_gl2psPrintSVGPrimitive,
  tools_gl2psPrintSVGFinalPrimitive,
  "svg",
  "Scalable Vector Graphics"
};

/*********************************************************************
 *
 * PGF routines
 *
 *********************************************************************/

inline void tools_gl2psPrintPGFColor(tools_GL2PScontext* gl2ps, tools_GL2PSrgba rgba)
{
  if(!tools_gl2psSameColor(gl2ps->lastrgba, rgba)){
    tools_gl2psSetLastColor(gl2ps, rgba);
    fprintf(gl2ps->stream, "\\color[rgb]{%f,%f,%f}\n", rgba[0], rgba[1], rgba[2]);
  }
}

inline void tools_gl2psPrintPGFHeader(tools_GL2PScontext* gl2ps)
{
  time_t now;

  time(&now);

  fprintf(gl2ps->stream,
          "%% Title: %s\n"
          "%% Creator: GL2PS %d.%d.%d%s, %s\n"
          "%% For: %s\n"
          "%% CreationDate: %s",
          gl2ps->title, TOOLS_GL2PS_MAJOR_VERSION, TOOLS_GL2PS_MINOR_VERSION,
          TOOLS_GL2PS_PATCH_VERSION, TOOLS_GL2PS_EXTRA_VERSION, TOOLS_GL2PS_COPYRIGHT,
          gl2ps->producer, ctime(&now));

  fprintf(gl2ps->stream, "\\begin{pgfpicture}\n");
  if(gl2ps->options & TOOLS_GL2PS_DRAW_BACKGROUND){
    tools_gl2psPrintPGFColor(gl2ps, gl2ps->bgcolor);
    fprintf(gl2ps->stream,
            "\\pgfpathrectanglecorners{"
            "\\pgfpoint{%dpt}{%dpt}}{\\pgfpoint{%dpt}{%dpt}}\n"
            "\\pgfusepath{fill}\n",
            (int)gl2ps->viewport[0], (int)gl2ps->viewport[1],
            (int)gl2ps->viewport[2], (int)gl2ps->viewport[3]);
  }
}

inline void tools_gl2psPrintPGFDash(tools_GL2PScontext* gl2ps, tools_GLushort pattern, tools_GLint factor)
{
  int i, n, array[10];

  if(pattern == gl2ps->lastpattern && factor == gl2ps->lastfactor)
    return;

  gl2ps->lastpattern = pattern;
  gl2ps->lastfactor = factor;

  if(!pattern || !factor){
    /* solid line */
    fprintf(gl2ps->stream, "\\pgfsetdash{}{0pt}\n");
  }
  else{
    tools_gl2psParseStipplePattern(pattern, factor, &n, array);
    fprintf(gl2ps->stream, "\\pgfsetdash{");
    for(i = 0; i < n; i++) fprintf(gl2ps->stream, "{%dpt}", array[i]);
    fprintf(gl2ps->stream, "}{0pt}\n");
  }
}

inline const char *tools_gl2psPGFTextAlignment(int align)
{
  switch(align){
  case TOOLS_GL2PS_TEXT_C  : return "center";
  case TOOLS_GL2PS_TEXT_CL : return "west";
  case TOOLS_GL2PS_TEXT_CR : return "east";
  case TOOLS_GL2PS_TEXT_B  : return "south";
  case TOOLS_GL2PS_TEXT_BR : return "south east";
  case TOOLS_GL2PS_TEXT_T  : return "north";
  case TOOLS_GL2PS_TEXT_TL : return "north west";
  case TOOLS_GL2PS_TEXT_TR : return "north east";
  case TOOLS_GL2PS_TEXT_BL :
  default            : return "south west";
  }
}

inline void tools_gl2psPrintPGFPrimitive(tools_GL2PScontext* gl2ps, void *data)
{
  tools_GL2PSprimitive *prim;

  prim = *(tools_GL2PSprimitive**)data;

  switch(prim->type){
  case TOOLS_GL2PS_POINT :
    /* Points in openGL are rectangular */
    tools_gl2psPrintPGFColor(gl2ps, prim->verts[0].rgba);
    fprintf(gl2ps->stream,
            "\\pgfpathrectangle{\\pgfpoint{%fpt}{%fpt}}"
            "{\\pgfpoint{%fpt}{%fpt}}\n\\pgfusepath{fill}\n",
            prim->verts[0].xyz[0]-0.5*prim->width,
            prim->verts[0].xyz[1]-0.5*prim->width,
            prim->width,prim->width);
    break;
  case TOOLS_GL2PS_LINE :
    tools_gl2psPrintPGFColor(gl2ps, prim->verts[0].rgba);
    if(gl2ps->lastlinewidth != prim->width){
      gl2ps->lastlinewidth = prim->width;
      fprintf(gl2ps->stream, "\\pgfsetlinewidth{%fpt}\n", gl2ps->lastlinewidth);
    }
    if(gl2ps->lastlinecap != prim->linecap){
      gl2ps->lastlinecap = prim->linecap;
      switch (prim->linecap){
      case TOOLS_GL2PS_LINE_CAP_BUTT:
        fprintf(gl2ps->stream, "\\pgfset%s\n", "buttcap");
        break;
      case TOOLS_GL2PS_LINE_CAP_ROUND:
        fprintf(gl2ps->stream, "\\pgfset%s\n", "roundcap");
        break;
      case TOOLS_GL2PS_LINE_CAP_SQUARE:
        fprintf(gl2ps->stream, "\\pgfset%s\n", "rectcap");
        break;
      }
    }
    if(gl2ps->lastlinejoin != prim->linejoin){
      gl2ps->lastlinejoin = prim->linejoin;
      switch (prim->linejoin){
      case TOOLS_GL2PS_LINE_JOIN_MITER:
        fprintf(gl2ps->stream, "\\pgfset%s\n", "miterjoin");
        break;
      case TOOLS_GL2PS_LINE_JOIN_ROUND:
        fprintf(gl2ps->stream, "\\pgfset%s\n", "roundjoin");
        break;
      case TOOLS_GL2PS_LINE_JOIN_BEVEL:
        fprintf(gl2ps->stream, "\\pgfset%s\n", "beveljoin");
        break;
      }
    }
    tools_gl2psPrintPGFDash(gl2ps, prim->pattern, prim->factor);
    fprintf(gl2ps->stream,
            "\\pgfpathmoveto{\\pgfpoint{%fpt}{%fpt}}\n"
            "\\pgflineto{\\pgfpoint{%fpt}{%fpt}}\n"
            "\\pgfusepath{stroke}\n",
            prim->verts[1].xyz[0], prim->verts[1].xyz[1],
            prim->verts[0].xyz[0], prim->verts[0].xyz[1]);
    break;
  case TOOLS_GL2PS_TRIANGLE :
    if(gl2ps->lastlinewidth != 0){
      gl2ps->lastlinewidth = 0;
      fprintf(gl2ps->stream, "\\pgfsetlinewidth{0.01pt}\n");
    }
    if(gl2ps->lastlinecap != prim->linecap){
      gl2ps->lastlinecap = prim->linecap;
      switch (prim->linecap){
      case TOOLS_GL2PS_LINE_CAP_BUTT:
        fprintf(gl2ps->stream, "\\pgfset%s\n", "buttcap");
        break;
      case TOOLS_GL2PS_LINE_CAP_ROUND:
        fprintf(gl2ps->stream, "\\pgfset%s\n", "roundcap");
        break;
      case TOOLS_GL2PS_LINE_CAP_SQUARE:
        fprintf(gl2ps->stream, "\\pgfset%s\n", "rectcap");
        break;
      }
    }
    if(gl2ps->lastlinejoin != prim->linejoin){
      gl2ps->lastlinejoin = prim->linejoin;
      switch (prim->linejoin){
      case TOOLS_GL2PS_LINE_JOIN_MITER:
        fprintf(gl2ps->stream, "\\pgfset%s\n", "miterjoin");
        break;
      case TOOLS_GL2PS_LINE_JOIN_ROUND:
        fprintf(gl2ps->stream, "\\pgfset%s\n", "roundjoin");
        break;
      case TOOLS_GL2PS_LINE_JOIN_BEVEL:
        fprintf(gl2ps->stream, "\\pgfset%s\n", "beveljoin");
        break;
      }
    }
    tools_gl2psPrintPGFColor(gl2ps, prim->verts[0].rgba);
    fprintf(gl2ps->stream,
            "\\pgfpathmoveto{\\pgfpoint{%fpt}{%fpt}}\n"
            "\\pgflineto{\\pgfpoint{%fpt}{%fpt}}\n"
            "\\pgflineto{\\pgfpoint{%fpt}{%fpt}}\n"
            "\\pgfpathclose\n"
            "\\pgfusepath{fill,stroke}\n",
            prim->verts[2].xyz[0], prim->verts[2].xyz[1],
            prim->verts[1].xyz[0], prim->verts[1].xyz[1],
            prim->verts[0].xyz[0], prim->verts[0].xyz[1]);
    break;
  case TOOLS_GL2PS_TEXT :
    fprintf(gl2ps->stream, "{\n\\pgftransformshift{\\pgfpoint{%fpt}{%fpt}}\n",
            prim->verts[0].xyz[0], prim->verts[0].xyz[1]);

    if(prim->data.text->angle)
      fprintf(gl2ps->stream, "\\pgftransformrotate{%f}{", prim->data.text->angle);

    fprintf(gl2ps->stream, "\\pgfnode{rectangle}{%s}{\\fontsize{%d}{0}\\selectfont",
            tools_gl2psPGFTextAlignment(prim->data.text->alignment),
            prim->data.text->fontsize);

    fprintf(gl2ps->stream, "\\textcolor[rgb]{%g,%g,%g}{{%s}}",
            prim->verts[0].rgba[0], prim->verts[0].rgba[1],
            prim->verts[0].rgba[2], prim->data.text->str);

    fprintf(gl2ps->stream, "}{}{\\pgfusepath{discard}}");

    if(prim->data.text->angle)
       fprintf(gl2ps->stream, "}");

    fprintf(gl2ps->stream, "\n}\n");
    break;
  case TOOLS_GL2PS_SPECIAL :
    /* alignment contains the format for which the special output text
       is intended */
    if (prim->data.text->alignment == TOOLS_GL2PS_PGF)
      fprintf(gl2ps->stream, "%s\n", prim->data.text->str);
    break;
  default :
    break;
  }
}

inline void tools_gl2psPrintPGFFooter(tools_GL2PScontext* gl2ps)
{
  fprintf(gl2ps->stream, "\\end{pgfpicture}\n");
}

inline void tools_gl2psPrintPGFBeginViewport(tools_GL2PScontext* gl2ps, tools_GLint viewport[4])
{
  tools_GLint idx;
  tools_GLfloat rgba[4];
  int x = viewport[0], y = viewport[1], w = viewport[2], h = viewport[3];

  tools_glRenderMode(TOOLS_GL_FEEDBACK);

  tools_gl2psResetLineProperties(gl2ps);

  if(gl2ps->header){
    tools_gl2psPrintPGFHeader(gl2ps);
    gl2ps->header = TOOLS_GL_FALSE;
  }

  fprintf(gl2ps->stream, "\\begin{pgfscope}\n");
  if(gl2ps->options & TOOLS_GL2PS_DRAW_BACKGROUND){
    if(gl2ps->colormode == TOOLS_GL_RGBA || gl2ps->colorsize == 0){
      tools_glGetFloatv(TOOLS_GL_COLOR_CLEAR_VALUE, rgba);
    }
    else{
      tools_glGetIntegerv(TOOLS_GL_INDEX_CLEAR_VALUE, &idx);
      rgba[0] = gl2ps->colormap[idx][0];
      rgba[1] = gl2ps->colormap[idx][1];
      rgba[2] = gl2ps->colormap[idx][2];
      rgba[3] = 1.0F;
    }
    tools_gl2psPrintPGFColor(gl2ps, rgba);
    fprintf(gl2ps->stream,
            "\\pgfpathrectangle{\\pgfpoint{%dpt}{%dpt}}"
            "{\\pgfpoint{%dpt}{%dpt}}\n"
            "\\pgfusepath{fill}\n",
            x, y, w, h);
  }

  fprintf(gl2ps->stream,
          "\\pgfpathrectangle{\\pgfpoint{%dpt}{%dpt}}"
          "{\\pgfpoint{%dpt}{%dpt}}\n"
          "\\pgfusepath{clip}\n",
          x, y, w, h);
}

inline tools_GLint tools_gl2psPrintPGFEndViewport(tools_GL2PScontext* gl2ps)
{
  tools_GLint res;
  res = tools_gl2psPrintPrimitives(gl2ps);
  fprintf(gl2ps->stream, "\\end{pgfscope}\n");
  return res;
}

inline void tools_gl2psPrintPGFFinalPrimitive(tools_GL2PScontext*)
{
}

/* definition of the PGF backend */

static const tools_GL2PSbackend tools_gl2psPGF = {
  tools_gl2psPrintPGFHeader,
  tools_gl2psPrintPGFFooter,
  tools_gl2psPrintPGFBeginViewport,
  tools_gl2psPrintPGFEndViewport,
  tools_gl2psPrintPGFPrimitive,
  tools_gl2psPrintPGFFinalPrimitive,
  "tex",
  "PGF Latex Graphics"
};

/*********************************************************************
 *
 * General primitive printing routine
 *
 *********************************************************************/

/* Warning: the ordering of the backends must match the format
   #defines in gl2ps.h */

static const tools_GL2PSbackend *tools_gl2psbackends[] = {
  &tools_gl2psPS,  /* 0 */
  &tools_gl2psEPS, /* 1 */
  &tools_gl2psTEX, /* 2 */
  &tools_gl2psPDF, /* 3 */
  &tools_gl2psSVG, /* 4 */
  &tools_gl2psPGF  /* 5 */
};

inline void tools_gl2psComputeTightBoundingBox(tools_GL2PScontext* gl2ps, void *data)
{
  tools_GL2PSprimitive *prim;
  int i;

  prim = *(tools_GL2PSprimitive**)data;

  for(i = 0; i < prim->numverts; i++){
    if(prim->verts[i].xyz[0] < gl2ps->viewport[0])
      gl2ps->viewport[0] = (tools_GLint)prim->verts[i].xyz[0];
    if(prim->verts[i].xyz[0] > gl2ps->viewport[2])
      gl2ps->viewport[2] = (tools_GLint)(prim->verts[i].xyz[0] + 0.5F);
    if(prim->verts[i].xyz[1] < gl2ps->viewport[1])
      gl2ps->viewport[1] = (tools_GLint)prim->verts[i].xyz[1];
    if(prim->verts[i].xyz[1] > gl2ps->viewport[3])
      gl2ps->viewport[3] = (tools_GLint)(prim->verts[i].xyz[1] + 0.5F);
  }
}

inline tools_GLint tools_gl2psPrintPrimitives(tools_GL2PScontext* gl2ps)
{
  tools_GL2PSbsptree *root;
  tools_GL2PSxyz eye = {0.0F, 0.0F, 100.0F * TOOLS_GL2PS_ZSCALE};
  tools_GLint used = 0;

  if ((gl2ps->options & TOOLS_GL2PS_NO_OPENGL_CONTEXT) == TOOLS_GL2PS_NONE) {
    used = tools_glRenderMode(TOOLS_GL_RENDER);
  }

  if(used < 0){
    tools_gl2psMsg(TOOLS_GL2PS_INFO, "OpenGL feedback buffer overflow");
    return TOOLS_GL2PS_OVERFLOW;
  }

  if(used > 0)
    tools_gl2psParseFeedbackBuffer(gl2ps, used);

  tools_gl2psRescaleAndOffset(gl2ps);

  if(gl2ps->header){
    if(tools_gl2psListNbr(gl2ps->primitives) &&
       (gl2ps->options & TOOLS_GL2PS_TIGHT_BOUNDING_BOX)){
      gl2ps->viewport[0] = gl2ps->viewport[1] = 100000;
      gl2ps->viewport[2] = gl2ps->viewport[3] = -100000;
      tools_gl2psListActionContext(gl2ps, gl2ps->primitives, tools_gl2psComputeTightBoundingBox);
    }
    (tools_gl2psbackends[gl2ps->format]->printHeader)(gl2ps);
    gl2ps->header = TOOLS_GL_FALSE;
  }

  if(!tools_gl2psListNbr(gl2ps->primitives)){
    /* empty feedback buffer and/or nothing else to print */
    return TOOLS_GL2PS_NO_FEEDBACK;
  }

  switch(gl2ps->sort){
  case TOOLS_GL2PS_NO_SORT :
    tools_gl2psListActionContext(gl2ps, gl2ps->primitives, tools_gl2psbackends[gl2ps->format]->printPrimitive);
    tools_gl2psListAction(gl2ps->primitives, tools_gl2psFreePrimitive);
    /* reset the primitive list, waiting for the next viewport */
    tools_gl2psListReset(gl2ps->primitives);
    break;
  case TOOLS_GL2PS_SIMPLE_SORT :
    tools_gl2psListAssignSortIds(gl2ps->primitives);
    tools_gl2psListSort(gl2ps, gl2ps->primitives, tools_gl2psCompareDepth);
    if(gl2ps->options & TOOLS_GL2PS_OCCLUSION_CULL){
      tools_gl2psListActionInverseContext(gl2ps, gl2ps->primitives, tools_gl2psAddInImageTree);
      tools_gl2psFreeBspImageTree(&gl2ps->imagetree);
    }
    tools_gl2psListActionContext(gl2ps, gl2ps->primitives, tools_gl2psbackends[gl2ps->format]->printPrimitive);
    tools_gl2psListAction(gl2ps->primitives, tools_gl2psFreePrimitive);
    /* reset the primitive list, waiting for the next viewport */
    tools_gl2psListReset(gl2ps->primitives);
    break;
  case TOOLS_GL2PS_BSP_SORT :
    root = (tools_GL2PSbsptree*)tools_gl2psMalloc(sizeof(tools_GL2PSbsptree));
    tools_gl2psBuildBspTree(gl2ps, root, gl2ps->primitives);
    if(TOOLS_GL_TRUE == gl2ps->boundary) tools_gl2psBuildPolygonBoundary(root);
    if(gl2ps->options & TOOLS_GL2PS_OCCLUSION_CULL){
      tools_gl2psTraverseBspTree(gl2ps, root, eye, -TOOLS_GL2PS_EPSILON, tools_gl2psLess,
                           tools_gl2psAddInImageTree, 1);
      tools_gl2psFreeBspImageTree(&gl2ps->imagetree);
    }
    tools_gl2psTraverseBspTree(gl2ps, root, eye, TOOLS_GL2PS_EPSILON, tools_gl2psGreater,
                         tools_gl2psbackends[gl2ps->format]->printPrimitive, 0);
    tools_gl2psFreeBspTree(&root);
    /* reallocate the primitive list (it's been deleted by
       tools_gl2psBuildBspTree) in case there is another viewport */
    gl2ps->primitives = tools_gl2psListCreate(500, 500, sizeof(tools_GL2PSprimitive*));
    break;
  }
  tools_gl2psbackends[gl2ps->format]->printFinalPrimitive(gl2ps);

  return TOOLS_GL2PS_SUCCESS;
}

inline tools_GLboolean tools_gl2psCheckOptions(tools_GLint options, tools_GLint colormode)
{
  if (options & TOOLS_GL2PS_NO_OPENGL_CONTEXT) {
    if (options & TOOLS_GL2PS_DRAW_BACKGROUND) {
      tools_gl2psMsg(TOOLS_GL2PS_ERROR, "Options TOOLS_GL2PS_NO_OPENGL_CONTEXT and "
                            "TOOLS_GL2PS_DRAW_BACKGROUND are incompatible.");
      return TOOLS_GL_FALSE;
    }
    if (options & TOOLS_GL2PS_USE_CURRENT_VIEWPORT) {
      tools_gl2psMsg(TOOLS_GL2PS_ERROR, "Options TOOLS_GL2PS_NO_OPENGL_CONTEXT and "
                            "TOOLS_GL2PS_USE_CURRENT_VIEWPORT are incompatible.");
      return TOOLS_GL_FALSE;
    }
    if ((options & TOOLS_GL2PS_NO_BLENDING) == TOOLS_GL2PS_NONE) {
      tools_gl2psMsg(TOOLS_GL2PS_ERROR, "Option TOOLS_GL2PS_NO_OPENGL_CONTEXT requires "
                            "option TOOLS_GL2PS_NO_BLENDING.");
      return TOOLS_GL_FALSE;
    }
    if (colormode != TOOLS_GL_RGBA) {
      tools_gl2psMsg(TOOLS_GL2PS_ERROR, "Option TOOLS_GL2PS_NO_OPENGL_CONTEXT requires colormode "
                            "to be TOOLS_GL_RGBA.");
      return TOOLS_GL_FALSE;
    }
  }

  return TOOLS_GL_TRUE;
}

/*********************************************************************
 *
 * Public routines
 *
 *********************************************************************/

TOOLS_GL2PSDLL_API tools_GLint tools_gl2psBeginPage(tools_GL2PScontext* gl2ps, const char *title, const char *producer,
                                  tools_GLint viewport[4], tools_GLint format, tools_GLint sort,
                                  tools_GLint options, tools_GLint colormode,
                                  tools_GLint colorsize, tools_GL2PSrgba *colormap,
                                  tools_GLint nr, tools_GLint ng, tools_GLint nb, tools_GLint buffersize,
                                  FILE *stream, const char *filename)
{
  tools_GLint idx;
  int i;

/*G.Barrand: if(gl2ps){
    tools_gl2psMsg(TOOLS_GL2PS_ERROR, "tools_gl2psBeginPage called in wrong program state");
    return TOOLS_GL2PS_ERROR;
  }

  gl2ps = (tools_GL2PScontext*)tools_gl2psMalloc(sizeof(tools_GL2PScontext));
*/

  /* Validate options */
  if (tools_gl2psCheckOptions(options, colormode) == TOOLS_GL_FALSE) {
  /*tools_gl2psFree(gl2ps);
    gl2ps = NULL;*/
    return TOOLS_GL2PS_ERROR;
  }

  if(format >= 0 && format < (tools_GLint)(sizeof(tools_gl2psbackends) / sizeof(tools_gl2psbackends[0]))){
    gl2ps->format = format;
  }
  else {
    tools_gl2psMsg(TOOLS_GL2PS_ERROR, "Unknown output format: %d", format);
  /*tools_gl2psFree(gl2ps);
    gl2ps = NULL;*/
    return TOOLS_GL2PS_ERROR;
  }

  switch(sort){
  case TOOLS_GL2PS_NO_SORT :
  case TOOLS_GL2PS_SIMPLE_SORT :
  case TOOLS_GL2PS_BSP_SORT :
    gl2ps->sort = sort;
    break;
  default :
    tools_gl2psMsg(TOOLS_GL2PS_ERROR, "Unknown sorting algorithm: %d", sort);
  /*tools_gl2psFree(gl2ps);
    gl2ps = NULL;*/
    return TOOLS_GL2PS_ERROR;
  }

  if(stream){
    gl2ps->stream = stream;
  }
  else{
    tools_gl2psMsg(TOOLS_GL2PS_ERROR, "Bad file pointer");
  /*tools_gl2psFree(gl2ps);
    gl2ps = NULL;*/
    return TOOLS_GL2PS_ERROR;
  }

  gl2ps->header = TOOLS_GL_TRUE;
  gl2ps->forcerasterpos = TOOLS_GL_FALSE;
  gl2ps->maxbestroot = 10;
  gl2ps->options = options;
  gl2ps->compress = NULL;
  gl2ps->imagemap_head = NULL;
  gl2ps->imagemap_tail = NULL;

  if(gl2ps->options & TOOLS_GL2PS_USE_CURRENT_VIEWPORT){
    tools_glGetIntegerv(TOOLS_GL_VIEWPORT, gl2ps->viewport);
  }
  else{
    for(i = 0; i < 4; i++){
      gl2ps->viewport[i] = viewport[i];
    }
  }

  if(!gl2ps->viewport[2] || !gl2ps->viewport[3]){
    tools_gl2psMsg(TOOLS_GL2PS_ERROR, "Incorrect viewport (x=%d, y=%d, width=%d, height=%d)",
             gl2ps->viewport[0], gl2ps->viewport[1],
             gl2ps->viewport[2], gl2ps->viewport[3]);
  /*tools_gl2psFree(gl2ps);
    gl2ps = NULL;*/
    return TOOLS_GL2PS_ERROR;
  }

  gl2ps->threshold[0] = nr ? 1.0F / (tools_GLfloat)nr : 0.064F;
  gl2ps->threshold[1] = ng ? 1.0F / (tools_GLfloat)ng : 0.034F;
  gl2ps->threshold[2] = nb ? 1.0F / (tools_GLfloat)nb : 0.100F;
  gl2ps->colormode = colormode;
  gl2ps->buffersize = buffersize > 0 ? buffersize : 2048 * 2048;
  for(i = 0; i < 3; i++){
    gl2ps->lastvertex.xyz[i] = -1.0F;
  }
  for(i = 0; i < 4; i++){
    gl2ps->lastvertex.rgba[i] = -1.0F;
    gl2ps->lastrgba[i] = -1.0F;
  }
  gl2ps->lastlinewidth = -1.0F;
  gl2ps->lastlinecap = 0;
  gl2ps->lastlinejoin = 0;
  gl2ps->lastpattern = 0;
  gl2ps->lastfactor = 0;
  gl2ps->imagetree = NULL;
  gl2ps->primitivetoadd = NULL;
  gl2ps->zerosurfacearea = TOOLS_GL_FALSE;
  gl2ps->pdfprimlist = NULL;
  gl2ps->pdfgrouplist = NULL;
  gl2ps->xreflist = NULL;

  /* get default blending mode from current OpenGL state (enabled by
     default for SVG) */
  if ((gl2ps->options & TOOLS_GL2PS_NO_BLENDING) == TOOLS_GL2PS_NONE) {
    gl2ps->blending = (gl2ps->format == TOOLS_GL2PS_SVG) ? TOOLS_GL_TRUE
                                                   : tools_glIsEnabled(TOOLS_GL_BLEND);
    tools_glGetIntegerv(TOOLS_GL_BLEND_SRC, &gl2ps->blendfunc[0]);
    tools_glGetIntegerv(TOOLS_GL_BLEND_DST, &gl2ps->blendfunc[1]);
  }
  else {
    gl2ps->blending = TOOLS_GL_FALSE;
  }

  if(gl2ps->colormode == TOOLS_GL_RGBA){
    gl2ps->colorsize = 0;
    gl2ps->colormap = NULL;
    if ((gl2ps->options & TOOLS_GL2PS_NO_OPENGL_CONTEXT) == TOOLS_GL2PS_NONE) {
      tools_glGetFloatv(TOOLS_GL_COLOR_CLEAR_VALUE, gl2ps->bgcolor);
    }
  }
  else if(gl2ps->colormode == TOOLS_GL_COLOR_INDEX){
    if(!colorsize || !colormap){
      tools_gl2psMsg(TOOLS_GL2PS_ERROR, "Missing colormap for TOOLS_GL_COLOR_INDEX rendering");
    /*tools_gl2psFree(gl2ps);
      gl2ps = NULL;*/
      return TOOLS_GL2PS_ERROR;
    }
    gl2ps->colorsize = colorsize;
    gl2ps->colormap = (tools_GL2PSrgba*)tools_gl2psMalloc(gl2ps->colorsize * sizeof(tools_GL2PSrgba));
    memcpy(gl2ps->colormap, colormap, gl2ps->colorsize * sizeof(tools_GL2PSrgba));
    tools_glGetIntegerv(TOOLS_GL_INDEX_CLEAR_VALUE, &idx);
    gl2ps->bgcolor[0] = gl2ps->colormap[idx][0];
    gl2ps->bgcolor[1] = gl2ps->colormap[idx][1];
    gl2ps->bgcolor[2] = gl2ps->colormap[idx][2];
    gl2ps->bgcolor[3] = 1.0F;
  }
  else{
    tools_gl2psMsg(TOOLS_GL2PS_ERROR, "Unknown color mode in tools_gl2psBeginPage");
  /*tools_gl2psFree(gl2ps);
    gl2ps = NULL;*/
    return TOOLS_GL2PS_ERROR;
  }

  if(!title){
    gl2ps->title = (char*)tools_gl2psMalloc(sizeof(char));
    gl2ps->title[0] = '\0';
  }
  else{
    gl2ps->title = (char*)tools_gl2psMalloc((strlen(title)+1)*sizeof(char));
    strcpy(gl2ps->title, title);
  }

  if(!producer){
    gl2ps->producer = (char*)tools_gl2psMalloc(sizeof(char));
    gl2ps->producer[0] = '\0';
  }
  else{
    gl2ps->producer = (char*)tools_gl2psMalloc((strlen(producer)+1)*sizeof(char));
    strcpy(gl2ps->producer, producer);
  }

  if(!filename){
    gl2ps->filename = (char*)tools_gl2psMalloc(sizeof(char));
    gl2ps->filename[0] = '\0';
  }
  else{
    gl2ps->filename = (char*)tools_gl2psMalloc((strlen(filename)+1)*sizeof(char));
    strcpy(gl2ps->filename, filename);
  }

  gl2ps->primitives = tools_gl2psListCreate(500, 500, sizeof(tools_GL2PSprimitive*));
  gl2ps->auxprimitives = tools_gl2psListCreate(100, 100, sizeof(tools_GL2PSprimitive*));

  if ((gl2ps->options & TOOLS_GL2PS_NO_OPENGL_CONTEXT) == TOOLS_GL2PS_NONE) {
    gl2ps->feedback = (tools_GLfloat*)tools_gl2psMalloc(gl2ps->buffersize * sizeof(tools_GLfloat));
    tools_glFeedbackBuffer(gl2ps->buffersize, TOOLS_GL_3D_COLOR, gl2ps->feedback);
    tools_glRenderMode(TOOLS_GL_FEEDBACK);
  }
  else {
    gl2ps->feedback = NULL;
    gl2ps->buffersize = 0;
  }

  gl2ps->tex_scaling = 1.;

  return TOOLS_GL2PS_SUCCESS;
}

TOOLS_GL2PSDLL_API tools_GLint tools_gl2psEndPage(tools_GL2PScontext* gl2ps)
{
  tools_GLint res;

/*if(!gl2ps) return TOOLS_GL2PS_UNINITIALIZED;*/

  res = tools_gl2psPrintPrimitives(gl2ps);

  if(res != TOOLS_GL2PS_OVERFLOW)
    (tools_gl2psbackends[gl2ps->format]->printFooter)(gl2ps);

  fflush(gl2ps->stream);

  tools_gl2psListDelete(gl2ps->primitives);
  tools_gl2psListDelete(gl2ps->auxprimitives);
  tools_gl2psFreeImagemap(gl2ps->imagemap_head);
  tools_gl2psFree(gl2ps->colormap);
  tools_gl2psFree(gl2ps->title);
  tools_gl2psFree(gl2ps->producer);
  tools_gl2psFree(gl2ps->filename);
  tools_gl2psFree(gl2ps->feedback);
/*tools_gl2psFree(gl2ps);
  gl2ps = NULL;*/

  return res;
}

TOOLS_GL2PSDLL_API tools_GLint tools_gl2psBeginViewport(tools_GL2PScontext* gl2ps, tools_GLint viewport[4])
{
/*if(!gl2ps) return TOOLS_GL2PS_UNINITIALIZED;*/

  (tools_gl2psbackends[gl2ps->format]->beginViewport)(gl2ps, viewport);

  return TOOLS_GL2PS_SUCCESS;
}

TOOLS_GL2PSDLL_API tools_GLint tools_gl2psEndViewport(tools_GL2PScontext* gl2ps)
{
  tools_GLint res;

/*if(!gl2ps) return TOOLS_GL2PS_UNINITIALIZED;*/

  res = (tools_gl2psbackends[gl2ps->format]->endViewport)(gl2ps);

  /* reset last used colors, line widths */
  tools_gl2psResetLineProperties(gl2ps);

  return res;
}

TOOLS_GL2PSDLL_API tools_GLint tools_gl2psSorting(tools_GL2PScontext* gl2ps, tools_GLint mode)
{
  tools_GLint res;

/*if(!gl2ps) return TOOLS_GL2PS_UNINITIALIZED;*/

  switch(mode){
  case TOOLS_GL2PS_NO_SORT :
  case TOOLS_GL2PS_SIMPLE_SORT :
  case TOOLS_GL2PS_BSP_SORT :
    gl2ps->sort = mode;
    res = TOOLS_GL2PS_SUCCESS;
    break;
  default :
    tools_gl2psMsg(TOOLS_GL2PS_ERROR, "Unknown sorting algorithm: %d", mode);
  /*tools_gl2psFree(gl2ps);
    gl2ps = NULL;*/
    res = TOOLS_GL2PS_ERROR;
  }

  return res;
}

TOOLS_GL2PSDLL_API tools_GLint tools_gl2psTextOptColor(tools_GL2PScontext* gl2ps, const char *str, const char *fontname,
                                     tools_GLshort fontsize, tools_GLint alignment, tools_GLfloat angle,
                                     tools_GL2PSrgba color)
{
  return tools_gl2psAddText(gl2ps, TOOLS_GL2PS_TEXT, str, fontname, fontsize, alignment, angle,
                      color, TOOLS_GL_FALSE, 0, 0);
}

/**
 * This version of tools_gl2psTextOptColor is used to go around the
 * fact that PDF does not support text alignment. The extra parameters
 * (blx, bly) represent the bottom left corner of the text bounding box.
 */
TOOLS_GL2PSDLL_API tools_GLint tools_gl2psTextOptColorBL(tools_GL2PScontext* gl2ps, const char *str, const char *fontname,
                                       tools_GLshort fontsize, tools_GLint alignment, tools_GLfloat angle,
                                       tools_GL2PSrgba color, tools_GLfloat blx, tools_GLfloat bly)
{
  return tools_gl2psAddText(gl2ps, TOOLS_GL2PS_TEXT, str, fontname, fontsize, alignment, angle,
                      color, TOOLS_GL_TRUE, blx, bly);
}

TOOLS_GL2PSDLL_API tools_GLint tools_gl2psTextOpt(tools_GL2PScontext* gl2ps, const char *str, const char *fontname,
                                tools_GLshort fontsize, tools_GLint alignment, tools_GLfloat angle)
{
  return tools_gl2psAddText(gl2ps, TOOLS_GL2PS_TEXT, str, fontname, fontsize, alignment, angle, NULL, TOOLS_GL_FALSE, 0, 0);
}

TOOLS_GL2PSDLL_API tools_GLint tools_gl2psText(tools_GL2PScontext* gl2ps, const char *str, const char *fontname, tools_GLshort fontsize)
{
  return tools_gl2psAddText(gl2ps, TOOLS_GL2PS_TEXT, str, fontname, fontsize, TOOLS_GL2PS_TEXT_BL, 0.0F,
                      NULL, TOOLS_GL_FALSE, 0, 0);
}

TOOLS_GL2PSDLL_API tools_GLint tools_gl2psSpecial(tools_GL2PScontext* gl2ps, tools_GLint format, const char *str)
{
  return tools_gl2psAddText(gl2ps, TOOLS_GL2PS_SPECIAL, str, "", 0, format, 0.0F, NULL, TOOLS_GL_FALSE, 0, 0);
}

TOOLS_GL2PSDLL_API tools_GLint tools_gl2psSpecialColor(tools_GL2PScontext* gl2ps, tools_GLint format, const char *str, tools_GL2PSrgba rgba)
{
  return tools_gl2psAddText(gl2ps, TOOLS_GL2PS_SPECIAL, str, "", 0, format, 0.0F, rgba, TOOLS_GL_FALSE, 0, 0);
}

TOOLS_GL2PSDLL_API tools_GLint tools_gl2psDrawPixels(tools_GL2PScontext* gl2ps, tools_GLsizei width, tools_GLsizei height,
                                   tools_GLint xorig, tools_GLint yorig,
                                   tools_GLenum format, tools_GLenum type,
                                   const void *pixels)
{
  int size, i;
  const tools_GLfloat *piv;
  tools_GLfloat pos[4], zoom_x, zoom_y;
  tools_GL2PSprimitive *prim;
  tools_GLboolean valid;

  if(/*!gl2ps ||*/ !pixels) return TOOLS_GL2PS_UNINITIALIZED;

  if((width <= 0) || (height <= 0)) return TOOLS_GL2PS_ERROR;

  if(gl2ps->options & TOOLS_GL2PS_NO_PIXMAP) return TOOLS_GL2PS_SUCCESS;

  if((format != TOOLS_GL_RGB && format != TOOLS_GL_RGBA) || type != TOOLS_GL_FLOAT){
    tools_gl2psMsg(TOOLS_GL2PS_ERROR, "tools_gl2psDrawPixels only implemented for "
             "TOOLS_GL_RGB/TOOLS_GL_RGBA, TOOLS_GL_FLOAT pixels");
    return TOOLS_GL2PS_ERROR;
  }

  if (gl2ps->forcerasterpos) {
    pos[0] = gl2ps->rasterpos.xyz[0];
    pos[1] = gl2ps->rasterpos.xyz[1];
    pos[2] = gl2ps->rasterpos.xyz[2];
    pos[3] = 1.f;
    /* Hardcode zoom factors (for now?) */
    zoom_x = 1.f;
    zoom_y = 1.f;
  }
  else {
    tools_glGetBooleanv(TOOLS_GL_CURRENT_RASTER_POSITION_VALID, &valid);
    if(TOOLS_GL_FALSE == valid) return TOOLS_GL2PS_SUCCESS; /* the primitive is culled */
    tools_glGetFloatv(TOOLS_GL_CURRENT_RASTER_POSITION, pos);
    tools_glGetFloatv(TOOLS_GL_ZOOM_X, &zoom_x);
    tools_glGetFloatv(TOOLS_GL_ZOOM_Y, &zoom_y);
  }

  prim = (tools_GL2PSprimitive*)tools_gl2psMalloc(sizeof(tools_GL2PSprimitive));
  prim->type = TOOLS_GL2PS_PIXMAP;
  prim->boundary = 0;
  prim->numverts = 1;
  prim->verts = (tools_GL2PSvertex*)tools_gl2psMalloc(sizeof(tools_GL2PSvertex));
  prim->verts[0].xyz[0] = pos[0] + xorig;
  prim->verts[0].xyz[1] = pos[1] + yorig;
  prim->verts[0].xyz[2] = pos[2];
  prim->culled = 0;
  prim->offset = 0;
  prim->ofactor = 0.0;
  prim->ounits = 0.0;
  prim->pattern = 0;
  prim->factor = 0;
  prim->width = 1;
  if (gl2ps->forcerasterpos) {
    prim->verts[0].rgba[0] = gl2ps->rasterpos.rgba[0];
    prim->verts[0].rgba[1] = gl2ps->rasterpos.rgba[1];
    prim->verts[0].rgba[2] = gl2ps->rasterpos.rgba[2];
    prim->verts[0].rgba[3] = gl2ps->rasterpos.rgba[3];
  }
  else {
    tools_glGetFloatv(TOOLS_GL_CURRENT_RASTER_COLOR, prim->verts[0].rgba);
  }
  prim->data.image = (tools_GL2PSimage*)tools_gl2psMalloc(sizeof(tools_GL2PSimage));
  prim->data.image->width = width;
  prim->data.image->height = height;
  prim->data.image->zoom_x = zoom_x;
  prim->data.image->zoom_y = zoom_y;
  prim->data.image->format = format;
  prim->data.image->type = type;

  gl2ps->forcerasterpos = TOOLS_GL_FALSE;

  switch(format){
  case TOOLS_GL_RGBA:
    if(gl2ps->options & TOOLS_GL2PS_NO_BLENDING || !gl2ps->blending){
      /* special case: blending turned off */
      prim->data.image->format = TOOLS_GL_RGB;
      size = height * width * 3;
      prim->data.image->pixels = (tools_GLfloat*)tools_gl2psMalloc(size * sizeof(tools_GLfloat));
      piv = (const tools_GLfloat*)pixels;
      for(i = 0; i < size; ++i, ++piv){
        prim->data.image->pixels[i] = *piv;
        if(!((i + 1) % 3))
          ++piv;
      }
    }
    else{
      size = height * width * 4;
      prim->data.image->pixels = (tools_GLfloat*)tools_gl2psMalloc(size * sizeof(tools_GLfloat));
      memcpy(prim->data.image->pixels, pixels, size * sizeof(tools_GLfloat));
    }
    break;
  case TOOLS_GL_RGB:
  default:
    size = height * width * 3;
    prim->data.image->pixels = (tools_GLfloat*)tools_gl2psMalloc(size * sizeof(tools_GLfloat));
    memcpy(prim->data.image->pixels, pixels, size * sizeof(tools_GLfloat));
    break;
  }

  /* If no OpenGL context, just add directly to primitives */
  if ((gl2ps->options & TOOLS_GL2PS_NO_OPENGL_CONTEXT) == TOOLS_GL2PS_NONE) {
    tools_gl2psListAdd(gl2ps->auxprimitives, &prim);
    tools_glPassThrough(TOOLS_GL2PS_DRAW_PIXELS_TOKEN);
  }
  else {
    tools_gl2psListAdd(gl2ps->primitives, &prim);
  }

  return TOOLS_GL2PS_SUCCESS;
}

TOOLS_GL2PSDLL_API tools_GLint tools_gl2psDrawImageMap(tools_GL2PScontext* gl2ps, tools_GLsizei width, tools_GLsizei height,
                                     const tools_GLfloat position[3],
                                     const unsigned char *imagemap){
  int size, i;
  int sizeoffloat = sizeof(tools_GLfloat);

  if(/*!gl2ps ||*/ !imagemap) return TOOLS_GL2PS_UNINITIALIZED;

  if((width <= 0) || (height <= 0)) return TOOLS_GL2PS_ERROR;

  size = height + height * ((width - 1) / 8);
  tools_glPassThrough(TOOLS_GL2PS_IMAGEMAP_TOKEN);
  tools_glBegin(TOOLS_GL_POINTS);
  tools_glVertex3f(position[0], position[1],position[2]);
  tools_glEnd();
  tools_glPassThrough((tools_GLfloat)width);
  tools_glPassThrough((tools_GLfloat)height);
  for(i = 0; i < size; i += sizeoffloat){
    const float *value = (const float*)imagemap;
    tools_glPassThrough(*value);
    imagemap += sizeoffloat;
  }
  return TOOLS_GL2PS_SUCCESS;
}

TOOLS_GL2PSDLL_API tools_GLint tools_gl2psEnable(tools_GL2PScontext* gl2ps, tools_GLint mode)
{
  tools_GLint tmp;
  tools_GLfloat tmp2;

/*if(!gl2ps) return TOOLS_GL2PS_UNINITIALIZED;*/

  switch(mode){
  case TOOLS_GL2PS_POLYGON_OFFSET_FILL :
    tools_glPassThrough(TOOLS_GL2PS_BEGIN_OFFSET_TOKEN);
    tools_glGetFloatv(TOOLS_GL_POLYGON_OFFSET_FACTOR, &tmp2);
    tools_glPassThrough(tmp2);
    tools_glGetFloatv(TOOLS_GL_POLYGON_OFFSET_UNITS, &tmp2);
    tools_glPassThrough(tmp2);
    break;
  case TOOLS_GL2PS_POLYGON_BOUNDARY :
    tools_glPassThrough(TOOLS_GL2PS_BEGIN_BOUNDARY_TOKEN);
    break;
  case TOOLS_GL2PS_LINE_STIPPLE :
    tools_glPassThrough(TOOLS_GL2PS_BEGIN_STIPPLE_TOKEN);
    tools_glGetIntegerv(TOOLS_GL_LINE_STIPPLE_PATTERN, &tmp);
    tools_glPassThrough((tools_GLfloat)tmp);
    tools_glGetIntegerv(TOOLS_GL_LINE_STIPPLE_REPEAT, &tmp);
    tools_glPassThrough((tools_GLfloat)tmp);
    break;
  case TOOLS_GL2PS_BLEND :
    tools_glPassThrough(TOOLS_GL2PS_BEGIN_BLEND_TOKEN);
    break;
  default :
    tools_gl2psMsg(TOOLS_GL2PS_WARNING, "Unknown mode in tools_gl2psEnable: %d", mode);
    return TOOLS_GL2PS_WARNING;
  }

  return TOOLS_GL2PS_SUCCESS;
}

TOOLS_GL2PSDLL_API tools_GLint tools_gl2psDisable(tools_GL2PScontext* gl2ps, tools_GLint mode)
{
/*if(!gl2ps) return TOOLS_GL2PS_UNINITIALIZED;*/

  switch(mode){
  case TOOLS_GL2PS_POLYGON_OFFSET_FILL :
    tools_glPassThrough(TOOLS_GL2PS_END_OFFSET_TOKEN);
    break;
  case TOOLS_GL2PS_POLYGON_BOUNDARY :
    tools_glPassThrough(TOOLS_GL2PS_END_BOUNDARY_TOKEN);
    break;
  case TOOLS_GL2PS_LINE_STIPPLE :
    tools_glPassThrough(TOOLS_GL2PS_END_STIPPLE_TOKEN);
    break;
  case TOOLS_GL2PS_BLEND :
    tools_glPassThrough(TOOLS_GL2PS_END_BLEND_TOKEN);
    break;
  default :
    tools_gl2psMsg(TOOLS_GL2PS_WARNING, "Unknown mode in tools_gl2psDisable: %d", mode);
    return TOOLS_GL2PS_WARNING;
  }

  return TOOLS_GL2PS_SUCCESS;
}

TOOLS_GL2PSDLL_API tools_GLint tools_gl2psPointSize(tools_GL2PScontext* gl2ps, tools_GLfloat value)
{
/*if(!gl2ps) return TOOLS_GL2PS_UNINITIALIZED;*/

  tools_glPassThrough(TOOLS_GL2PS_POINT_SIZE_TOKEN);
  tools_glPassThrough(value);

  return TOOLS_GL2PS_SUCCESS;
}

TOOLS_GL2PSDLL_API tools_GLint tools_gl2psLineCap(tools_GL2PScontext* gl2ps, tools_GLint value)
{
/*if(!gl2ps) return TOOLS_GL2PS_UNINITIALIZED;*/

  tools_glPassThrough(TOOLS_GL2PS_LINE_CAP_TOKEN);
  tools_glPassThrough(value);

  return TOOLS_GL2PS_SUCCESS;
}

TOOLS_GL2PSDLL_API tools_GLint tools_gl2psLineJoin(tools_GL2PScontext* gl2ps, tools_GLint value)
{
/*if(!gl2ps) return TOOLS_GL2PS_UNINITIALIZED;*/

  tools_glPassThrough(TOOLS_GL2PS_LINE_JOIN_TOKEN);
  tools_glPassThrough((tools_GLfloat)value);  //G.Barrand : _MSC_VER : cast.

  return TOOLS_GL2PS_SUCCESS;
}

TOOLS_GL2PSDLL_API tools_GLint tools_gl2psLineWidth(tools_GL2PScontext* gl2ps, tools_GLfloat value)
{
/*if(!gl2ps) return TOOLS_GL2PS_UNINITIALIZED;*/

  tools_glPassThrough(TOOLS_GL2PS_LINE_WIDTH_TOKEN);
  tools_glPassThrough(value);

  return TOOLS_GL2PS_SUCCESS;
}

TOOLS_GL2PSDLL_API tools_GLint tools_gl2psBlendFunc(tools_GL2PScontext* gl2ps, tools_GLenum sfactor, tools_GLenum dfactor)
{
/*if(!gl2ps) return TOOLS_GL2PS_UNINITIALIZED;*/

  if(TOOLS_GL_FALSE == tools_gl2psSupportedBlendMode(sfactor, dfactor))
    return TOOLS_GL2PS_WARNING;

  tools_glPassThrough(TOOLS_GL2PS_SRC_BLEND_TOKEN);
  tools_glPassThrough((tools_GLfloat)sfactor);
  tools_glPassThrough(TOOLS_GL2PS_DST_BLEND_TOKEN);
  tools_glPassThrough((tools_GLfloat)dfactor);

  return TOOLS_GL2PS_SUCCESS;
}

TOOLS_GL2PSDLL_API tools_GLint tools_gl2psSetOptions(tools_GL2PScontext* gl2ps, tools_GLint options)
{
/*if(!gl2ps) return TOOLS_GL2PS_UNINITIALIZED;*/

  if(tools_gl2psCheckOptions(options, gl2ps->colormode) == TOOLS_GL_FALSE) {
    return TOOLS_GL2PS_ERROR;
  }

  gl2ps->options = options;

  return TOOLS_GL2PS_SUCCESS;
}

TOOLS_GL2PSDLL_API tools_GLint tools_gl2psGetOptions(tools_GL2PScontext* gl2ps, tools_GLint *options)
{
/*if(!gl2ps) {
    *options = 0;
    return TOOLS_GL2PS_UNINITIALIZED;
  }*/

  *options = gl2ps->options;

  return TOOLS_GL2PS_SUCCESS;
}

TOOLS_GL2PSDLL_API const char *tools_gl2psGetFileExtension(tools_GLint format)
{
  if(format >= 0 && format < (tools_GLint)(sizeof(tools_gl2psbackends) / sizeof(tools_gl2psbackends[0])))
    return tools_gl2psbackends[format]->file_extension;
  else
    return "Unknown format";
}

TOOLS_GL2PSDLL_API const char *tools_gl2psGetFormatDescription(tools_GLint format)
{
  if(format >= 0 && format < (tools_GLint)(sizeof(tools_gl2psbackends) / sizeof(tools_gl2psbackends[0])))
    return tools_gl2psbackends[format]->description;
  else
    return "Unknown format";
}

TOOLS_GL2PSDLL_API tools_GLint tools_gl2psGetFileFormat(tools_GL2PScontext* gl2ps)
{
/*if(!gl2ps) {
    return TOOLS_GL2PS_UNINITIALIZED;
  }*/

  return gl2ps->format;
}

TOOLS_GL2PSDLL_API tools_GLint tools_gl2psForceRasterPos(tools_GL2PScontext* gl2ps, tools_GL2PSvertex *vert)
{

/*if(!gl2ps) {
    return TOOLS_GL2PS_UNINITIALIZED;
  }*/

  gl2ps->forcerasterpos = TOOLS_GL_TRUE;
  gl2ps->rasterpos.xyz[0] = vert->xyz[0];
  gl2ps->rasterpos.xyz[1] = vert->xyz[1];
  gl2ps->rasterpos.xyz[2] = vert->xyz[2];
  gl2ps->rasterpos.rgba[0] = vert->rgba[0];
  gl2ps->rasterpos.rgba[1] = vert->rgba[1];
  gl2ps->rasterpos.rgba[2] = vert->rgba[2];
  gl2ps->rasterpos.rgba[3] = vert->rgba[3];

  return TOOLS_GL2PS_SUCCESS;
}

TOOLS_GL2PSDLL_API tools_GLint tools_gl2psSetTexScaling(tools_GL2PScontext* gl2ps, tools_GLfloat scaling)
{

/*if(!gl2ps) {
    return TOOLS_GL2PS_UNINITIALIZED;
  }*/
  gl2ps->tex_scaling = scaling;

  return TOOLS_GL2PS_SUCCESS;
}

TOOLS_GL2PSDLL_API tools_GLint tools_gl2psSetBackgroundColor(tools_GL2PScontext* gl2ps, float a_r,float a_g,float a_b)
{
/*if(!gl2ps) return TOOLS_GL2PS_UNINITIALIZED;*/

  gl2ps->bgcolor[0] = a_r;
  gl2ps->bgcolor[1] = a_g;
  gl2ps->bgcolor[2] = a_b;
  gl2ps->bgcolor[3] = 1.0F;

  return TOOLS_GL2PS_SUCCESS;
}

#undef TOOLS_GL2PS_EPSILON
#undef TOOLS_GL2PS_ZSCALE
#undef TOOLS_GL2PS_ZOFFSET
#undef TOOLS_GL2PS_ZOFFSET_LARGE
#undef TOOLS_GL2PS_ZERO
#undef TOOLS_GL2PS_COINCIDENT
#undef TOOLS_GL2PS_IN_FRONT_OF
#undef TOOLS_GL2PS_IN_BACK_OF
#undef TOOLS_GL2PS_SPANNING
#undef TOOLS_GL2PS_POINT_COINCIDENT
#undef TOOLS_GL2PS_POINT_INFRONT
#undef TOOLS_GL2PS_POINT_BACK
#undef TOOLS_GL2PS_BEGIN_OFFSET_TOKEN
#undef TOOLS_GL2PS_END_OFFSET_TOKEN
#undef TOOLS_GL2PS_BEGIN_BOUNDARY_TOKEN
#undef TOOLS_GL2PS_END_BOUNDARY_TOKEN
#undef TOOLS_GL2PS_BEGIN_STIPPLE_TOKEN
#undef TOOLS_GL2PS_END_STIPPLE_TOKEN
#undef TOOLS_GL2PS_POINT_SIZE_TOKEN
#undef TOOLS_GL2PS_LINE_CAP_TOKEN
#undef TOOLS_GL2PS_LINE_JOIN_TOKEN
#undef TOOLS_GL2PS_LINE_WIDTH_TOKEN
#undef TOOLS_GL2PS_BEGIN_BLEND_TOKEN
#undef TOOLS_GL2PS_END_BLEND_TOKEN
#undef TOOLS_GL2PS_SRC_BLEND_TOKEN
#undef TOOLS_GL2PS_DST_BLEND_TOKEN
#undef TOOLS_GL2PS_IMAGEMAP_TOKEN
#undef TOOLS_GL2PS_DRAW_PIXELS_TOKEN
#undef TOOLS_GL2PS_TEXT_TOKEN

#endif /*tools_gl2ps*/
