/* -*- mode: C; c-basic-offset: 2 -*- */
/*   EXTRAITS DE LA LICENCE
	Copyright CEA, contributeurs : Damien
	CALISTE, laboratoire L_Sim, (2001-2021)
  
	Adresse mèl :
	CALISTE, damien P caliste AT cea P fr.

	Ce logiciel est un programme informatique servant à visualiser des
	structures atomiques dans un rendu pseudo-3D. 

	Ce logiciel est régi par la licence CeCILL soumise au droit français et
	respectant les principes de diffusion des logiciels libres. Vous pouvez
	utiliser, modifier et/ou redistribuer ce programme sous les conditions
	de la licence CeCILL telle que diffusée par le CEA, le CNRS et l'INRIA 
	sur le site "http://www.cecill.info".

	Le fait que vous puissiez accéder à cet en-tête signifie que vous avez 
	pris connaissance de la licence CeCILL, et que vous en avez accepté les
	termes (cf. le fichier Documentation/licence.fr.txt fourni avec ce logiciel).
*/

/*   LICENCE SUM UP
	Copyright CEA, contributors : Damien
	CALISTE, laboratoire L_Sim, (2001-2021)

	E-mail address:
	CALISTE, damien P caliste AT cea P fr.

	This software is a computer program whose purpose is to visualize atomic
	configurations in 3D.

	This software is governed by the CeCILL  license under French law and
	abiding by the rules of distribution of free software.  You can  use, 
	modify and/ or redistribute the software under the terms of the CeCILL
	license as circulated by CEA, CNRS and INRIA at the following URL
	"http://www.cecill.info". 

	The fact that you are presently reading this means that you have had
	knowledge of the CeCILL license and that you accept its terms. You can
	find a copy of this licence shipped with this software at Documentation/licence.en.txt.
*/
#include "dumpToSVG.h"

/**
 * SECTION:dumpToSVG
 * @short_description: add an export capability into SVG files.
 * @include: extensions/box.h, extensions/axes.h, visu_pairs.h and visu_data.h
 *
 * This provides a write routine to export V_Sim views into SVG
 * files. Currently, this is an experimental feature. Not all V_Sim
 * elements are rendered, only the nodes, the box, the pairs and the
 * axes. All the characteristics are not used (no line stipple for
 * instance). In spin mode, nodes are only atomic.
 *
 * Since: 3.4
 */

#include <math.h>
#include <string.h>
#include <glib.h>
#include <cairo.h>

#include <visu_tools.h>
#include <visu_pairset.h>
#include <openGLFunctions/view.h>
#include <extensions/box.h>
#include <extensions/fogAndBGColor.h>
#include <extensions/axes.h>
#include <extensions/legend.h>
#include <extensions/pairs.h>
#include <pairsModeling/iface_wire.h>
#include <pairsModeling/wire_renderer.h>
#include <renderingMethods/elementAtomic.h>

#include <cairo-svg.h>
#include <cairo-pdf.h>

#define FONT_SIZE 16.
#define FONT_SMALL 12.

static gboolean writeViewInSvgFormat(ToolFileFormat *format, const char* filename,
                                     VisuGlNodeScene *scene, guint width, guint height,
                                     GError **error, ToolVoidDataFunc functionWait, gpointer data);
static gboolean writeViewInPdfFormat(ToolFileFormat *format, const char* filename,
                                     VisuGlNodeScene *scene, guint width, guint height,
                                     GError **error, ToolVoidDataFunc functionWait, gpointer data);

static cairo_pattern_t** svgSetup_patterns(VisuNodeArrayRenderer *array, gboolean flat);
static GList* svgSetup_pairs(VisuData *dataObj);
static GArray* svgCompute_pairs(const VisuGlExtPairs *pairs, const VisuGlView *view);
static GArray* svgCompute_coordinates(const VisuNodeArrayRenderer *renderer,
                                      const VisuGlView *view);
static GArray* svgCompute_box(const VisuBox *box, const VisuGlView *view);
static gboolean svgDraw_line(cairo_t *cr, const gfloat xyz1[3],
                             const gfloat xyz2[3], float *rgb,
                             gboolean useFog, float fog[4], float se[2]);
static void svgDraw_boxBack(cairo_t *cr, const GArray *box, gfloat val,
                            double width, float *rgb,
                            gboolean useFog, float fog[4], float se[2]);
static void svgDraw_boxFront(cairo_t *cr, const GArray *box, gfloat val,
                             double width, float *rgb,
                             gboolean useFog, float fog[4], float se[2]);
static void svgDraw_nodesAndPairs(cairo_t *cr, const GArray *coordinates,
				  const GArray *pairs, cairo_pattern_t **pat,
				  gboolean flat, gboolean useFog,
                                  float rgbaFog[4], float se[2]);
static void svgDraw_legend(cairo_t *cr, VisuGlExtLegend *extLegend, cairo_pattern_t **pat);
static void svgDraw_axes(cairo_t *cr, VisuGlExtAxes *extAxes);
static void svgDraw_pairs(cairo_t *cr, const GArray *pairs,
			  guint *iPairs, gfloat val,
                          gboolean useFog, float fog[4], float se[2]);
static void svgDraw_node(cairo_t *cr, VisuElementAtomicShapeId shape, float radius);

static VisuDump *svg;
static double g_width, g_height;
static double zoomLevel = 1.;
static VisuDumpCairoAdd postFunc = NULL;

/**
 * visu_dump_cairo_setPostFunc:
 * @func: (allow-none) (scope call): a #VisuDumpCairoAdd function or %NULL.
 *
 * Allow to add a function that will be called on every Cairo
 * exportation after V_Sim rendering to allow post-processing.
 *
 * Since: 3.7
 **/
void visu_dump_cairo_setPostFunc(VisuDumpCairoAdd func)
{
  g_debug("Visu DumpCairo: set a new post-processing method.");
  postFunc = func;
}

const VisuDump* visu_dump_cairo_svg_getStatic()
{
  const gchar *typeSVG[] = {"*.svg", (char*)0};
#define descrSVG _("Scalar Vector Graphic (SVG) file")

  if (svg)
    return svg;

  svg = VISU_DUMP(visu_dump_scene_new(descrSVG, typeSVG, writeViewInSvgFormat, TRUE));

  tool_file_format_addPropertyBoolean(TOOL_FILE_FORMAT(svg), "use_flat_rendering",
                                      _("Use flat colours for scheme rendering"),
                                      FALSE);
  
  return svg;
}
static VisuDump *pdf;
const VisuDump* visu_dump_cairo_pdf_getStatic()
{
  const gchar *typePDF[] = {"*.pdf", (char*)0};
#define descrPDF _("Portable Document Format (PDF) file")

  if (pdf)
    return pdf;

  pdf = VISU_DUMP(visu_dump_scene_new(descrPDF, typePDF, writeViewInPdfFormat, TRUE));

  tool_file_format_addPropertyBoolean(TOOL_FILE_FORMAT(pdf), "use_flat_rendering",
                                      _("Use flat colours for scheme rendering"),
                                      FALSE);
  
  return pdf;
}

static void sort_by_z(float *coordinates, float *buffer,
		      int n, int z, int begin, int end)
{
  int i;
  int middle;

  if( begin >= end ) return;
  
  memcpy(buffer, coordinates + begin * n, sizeof(float) * n);
  memcpy(coordinates + begin * n, coordinates + (end + begin) / 2 * n, sizeof(float) * n);
  memcpy(coordinates + (end + begin) / 2 * n, buffer, sizeof(float) * n);

  middle = begin;
  for(i = begin + 1; i <= end; i++)
    {
      if ( coordinates[i * n + z] > coordinates[begin * n + z] )
	{
	  middle += 1;
	  memcpy(buffer, coordinates + i * n, sizeof(float) * n);
	  memcpy(coordinates + i * n, coordinates + middle * n, sizeof(float) * n);
	  memcpy(coordinates + middle * n, buffer, sizeof(float) * n);
	}
    }
  memcpy(buffer, coordinates + begin * n, sizeof(float) * n);
  memcpy(coordinates + begin * n, coordinates + middle * n, sizeof(float) * n);
  memcpy(coordinates + middle * n, buffer, sizeof(float) * n);
  sort_by_z(coordinates, buffer, n, z, begin, middle - 1);
  sort_by_z(coordinates, buffer, n, z, middle + 1, end);
}

gboolean writeDataToCairoSurface(cairo_surface_t *cairo_surf, guint width, guint height,
                                 ToolFileFormat *format, VisuGlNodeScene *scene, GError **error,
				 ToolVoidDataFunc functionWait _U_, gpointer user_data _U_)
{
  cairo_t *cr;
  cairo_status_t status;
  cairo_pattern_t **pat;
  guint i;
  float rgbaBg[4], fog[4], se[2];
  GArray *box, *pairs, *coordinates;
  gboolean flat;
  cairo_matrix_t scale = {1., 0., 0., 1., 0., 0.};
  VisuData *dataObj;
  VisuGlView *view;
  double gross;
  guint oldW, oldH;
  VisuGlExtBox *extBox;
  VisuDataColorizer *colorizer;
  ToolGlMatrix P, M, MVP;

  g_debug("Dump SVG: begin OpenGL buffer writing.");

  /* We get the properties related to SVG output. */
  flat = tool_file_format_getPropertyBoolean(format, "use_flat_rendering");
  /* We setup the cairo output. */
  cr = cairo_create(cairo_surf);
  status = cairo_status(cr);
  if (status != CAIRO_STATUS_SUCCESS)
    {
      *error = g_error_new(VISU_DUMP_ERROR, DUMP_ERROR_FILE,
			   "%s", cairo_status_to_string(status));
      cairo_destroy(cr);
      return FALSE;
    }

  view = visu_gl_node_scene_getGlView(scene);

  /* We must change the viewport and zoom... */
  gross = view->camera.gross;
  oldW = visu_gl_view_getWidth(view);
  oldH = visu_gl_view_getHeight(view);
  /* We unzoom to allow GL to render out-of-the-box points. */
  zoomLevel = MAX(view->camera.gross, 1.f);
  visu_gl_view_setGross(view, MIN(view->camera.gross, 1.f));

  visu_gl_view_setViewport(view,
                           (width > 0) ? width : visu_gl_view_getWidth(view),
                           (height > 0) ? height : visu_gl_view_getHeight(view));
  visu_gl_view_getProjection(view, &P);
  visu_gl_view_getModelView(view, &M);
  visu_gl_view_getModelViewProjection(view, &MVP);

  dataObj = visu_gl_node_scene_getData(scene);

  cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
  g_debug("Dump SVG: setup scale and patterns.");
  pat = svgSetup_patterns(VISU_NODE_ARRAY_RENDERER(visu_gl_node_scene_getNodes(scene)), flat);
  g_debug(" | OK.");

  /* We calculate the node positions. */
  g_debug("Dump SVG: compute coordinates.");
  coordinates = svgCompute_coordinates(VISU_NODE_ARRAY_RENDERER(visu_gl_node_scene_getNodes(scene)), view);
  /* if (functionWait) */
  /*   functionWait(user_data); */

  /* Calculate the pair positions. */
  pairs = (GArray*)0;
  if (visu_gl_ext_getActive(VISU_GL_EXT(visu_gl_node_scene_getPairs(scene))))
    pairs = svgCompute_pairs(visu_gl_node_scene_getPairs(scene), view);
  /* if (functionWait) */
  /*   functionWait(user_data); */

  /* We draw the background colour. */
  visu_gl_getBgColor(VISU_GL(scene), rgbaBg);
  cairo_set_source_rgba(cr, rgbaBg[0], rgbaBg[1], rgbaBg[2], rgbaBg[3]);
  cairo_paint(cr);

  /* Experimental. */
  g_width = width;
  g_height = height;
  cairo_save(cr);
  cairo_scale(cr, zoomLevel, zoomLevel);
  cairo_translate(cr, g_width * (1. / zoomLevel - 1.) * 0.5,
                  g_height * (1. / zoomLevel - 1.) * 0.5);
  cairo_rectangle(cr, -g_width * (1. / zoomLevel - 1.) * 0.5,
                  -g_height * (1. / zoomLevel - 1.) * 0.5,
                  (g_width - 0.5) / zoomLevel, (g_height - 0.5) / zoomLevel);
  cairo_clip(cr);

  /* Draw the back box. */
  box = (GArray*)0;
  extBox = visu_gl_node_scene_getBox(scene);
  visu_gl_getFogColor(VISU_GL(scene), fog);
  visu_gl_getFogStartFull(VISU_GL(scene), se);
  if (visu_gl_ext_getActive(VISU_GL_EXT(extBox)))
    {
      box = svgCompute_box(visu_boxed_getBox(VISU_BOXED(visu_gl_node_scene_getData(scene))), view);
      svgDraw_boxBack(cr, box, 0.5f, /* coordinates[nNodes - NBUFF + 3], */
                      (double)visu_gl_ext_lined_getWidth(VISU_GL_EXT_LINED(extBox)) / zoomLevel,
                      visu_gl_ext_lined_getRGBA(VISU_GL_EXT_LINED(extBox)),
                      visu_gl_getFogActive(VISU_GL(scene)), fog, se);
    }
  /* if (functionWait) */
  /*   functionWait(user_data); */

  g_debug("Dump SVG: begin main SVG exportation.");

  /* We draw nodes and pairs. */
  colorizer = visu_node_array_renderer_getColorizer(VISU_NODE_ARRAY_RENDERER(visu_gl_node_scene_getNodes(scene)));
  svgDraw_nodesAndPairs(cr, coordinates, pairs,
			!(colorizer && visu_data_colorizer_getActive(colorizer)) ?
			pat:(cairo_pattern_t**)0, flat,
                        visu_gl_getFogActive(VISU_GL(scene)), fog, se);
  if (pairs)
    g_array_unref(pairs);
  /* if (functionWait) */
  /*   functionWait(user_data); */

  /* Draw the front box. */
  if (box)
    svgDraw_boxFront(cr, box, 0.5, /* coordinates[nNodes - NBUFF + 3], */
                     (double)visu_gl_ext_lined_getWidth(VISU_GL_EXT_LINED(extBox)) / zoomLevel,
                     visu_gl_ext_lined_getRGBA(VISU_GL_EXT_LINED(extBox)),
                     visu_gl_getFogActive(VISU_GL(scene)), fog, se);
  if (box)
    g_array_unref(box);
  /* if (functionWait) */
  /*   functionWait(user_data); */

  cairo_restore(cr);

  g_array_unref(coordinates);

  /* We draw the axes. */
  if (visu_gl_ext_getActive(VISU_GL_EXT(visu_gl_node_scene_getAxes(scene))))
    svgDraw_axes(cr, visu_gl_node_scene_getAxes(scene));
  /* if (functionWait) */
  /*   functionWait(user_data); */

  /* We draw a legend, if required. */
  if (visu_gl_ext_getActive(VISU_GL_EXT(visu_gl_node_scene_getLegend(scene))))
    svgDraw_legend(cr, visu_gl_node_scene_getLegend(scene), pat);
  /* if (functionWait) */
  /*   functionWait(user_data); */

  for (i = 0; i < visu_node_array_getNElements(VISU_NODE_ARRAY(dataObj), FALSE); i++)
    cairo_pattern_destroy(pat[i]);
  g_free(pat);

  if (postFunc)
    {
      g_debug("Visu DumpCairo: call the post-processing method.");
      cairo_set_matrix(cr, &scale);
      postFunc(cr, width, height);
    }

  cairo_show_page(cr);
  cairo_destroy(cr);

  /* We must put back the viewport... */
  visu_gl_view_setViewport(view, oldW, oldH);
  /* We must put back the gross also. */
  visu_gl_view_setGross(view, gross);

  return TRUE;
}

static gboolean writeViewInSvgFormat(ToolFileFormat *format, const char* filename,
                                     VisuGlNodeScene *scene, guint width, guint height,
                                     GError **error, ToolVoidDataFunc functionWait, gpointer data)
{
  cairo_surface_t *svg_surface;
  cairo_status_t status;
  gboolean valid;

  g_return_val_if_fail(error && !*error, FALSE);

  g_debug("Dump Cairo: begin export to SVG.");

  svg_surface = cairo_svg_surface_create(filename, (double)width, (double)height);
  status = cairo_surface_status(svg_surface);
  if (status != CAIRO_STATUS_SUCCESS)
    {
      *error = g_error_new(VISU_DUMP_ERROR, DUMP_ERROR_FILE,
			   "%s", cairo_status_to_string(status));
      cairo_surface_destroy(svg_surface);
      return FALSE;
    }

  valid = writeDataToCairoSurface(svg_surface, width, height, format, scene,
                                  error, functionWait, data);
  cairo_surface_destroy(svg_surface);

  return valid;
}

static gboolean writeViewInPdfFormat(ToolFileFormat *format, const char* filename,
				      VisuGlNodeScene *scene, guint width, guint height,
				      GError **error, ToolVoidDataFunc functionWait, gpointer data)
{
  cairo_surface_t *pdf_surface;
  cairo_status_t status;
  gboolean valid;

  g_return_val_if_fail(error && !*error, FALSE);

  g_debug("Dump Cairo: begin export to PDF.");

  pdf_surface = cairo_pdf_surface_create(filename, (double)width, (double)height);
  status = cairo_surface_status(pdf_surface);
  if (status != CAIRO_STATUS_SUCCESS)
    {
      *error = g_error_new(VISU_DUMP_ERROR, DUMP_ERROR_FILE,
			   "%s", cairo_status_to_string(status));
      cairo_surface_destroy(pdf_surface);
      return FALSE;
    }

  valid = writeDataToCairoSurface(pdf_surface, width, height, format, scene,
                                  error, functionWait, data);
  cairo_surface_destroy(pdf_surface);

  return valid;
}

static void svgDraw_legend(cairo_t *cr, VisuGlExtLegend *extLegend, cairo_pattern_t **pat)
{
  float xpad, ypad, max;
  guint iEle, nEle;
  gchar *lbl;
  gfloat radius;
  gboolean valid;
  VisuNodeArrayRenderer *nodes;
  VisuNodeArrayRendererIter iter;

  cairo_select_font_face(cr, "Serif", CAIRO_FONT_SLANT_NORMAL,
			 CAIRO_FONT_WEIGHT_NORMAL);
  cairo_set_font_size(cr, FONT_SIZE);
  cairo_set_line_width(cr, 1. / zoomLevel);

  g_object_get(extLegend, "x-padding", &xpad, "y-padding", &ypad, NULL);
  cairo_set_source_rgba(cr, 1.f, 1.f, 1.f, 0.4f);
  cairo_rectangle(cr, 10., 10., 2 * xpad + VISU_GL_EXT_FRAME(extLegend)->width,
                  2 * ypad + VISU_GL_EXT_FRAME(extLegend)->height);
  cairo_fill(cr);
  /* Run to the names and the elements... */
  nodes = visu_gl_ext_legend_getNodes(extLegend);
  max = visu_node_array_renderer_getMaxElementSize(nodes, &nEle);
  for (valid = visu_node_array_renderer_iter_new(nodes, &iter, TRUE), iEle = 0;
       valid; valid = visu_node_array_renderer_iter_next(&iter), iEle += 1)
    {
      cairo_text_extents_t extents;
      /* Write the name. */
      lbl = g_strdup_printf("%s (%d)", iter.element->name, iter.nStoredNodes);
      cairo_text_extents(cr, lbl, &extents);
      cairo_move_to(cr, 10. + xpad + VISU_GL_EXT_FRAME(extLegend)->height + 5 + iEle * VISU_GL_EXT_FRAME(extLegend)->width / nEle,
                    10. + ypad + 0.5f * VISU_GL_EXT_FRAME(extLegend)->height - (extents.height/2 + extents.y_bearing));
      cairo_set_source_rgb(cr, 0.f, 0.f, 0.f);
      cairo_show_text(cr, lbl);
      g_free(lbl);

      /* Draw the element. */
      radius = 0.4f * VISU_GL_EXT_FRAME(extLegend)->height * visu_element_atomic_getRadius(VISU_ELEMENT_ATOMIC(iter.renderer)) / max;
      cairo_new_path(cr);
      cairo_save(cr);
      cairo_translate(cr, 10. + xpad + 0.5f * VISU_GL_EXT_FRAME(extLegend)->height + iEle * VISU_GL_EXT_FRAME(extLegend)->width / nEle,
                      10. + ypad + 0.5f * VISU_GL_EXT_FRAME(extLegend)->height);
      svgDraw_node(cr, visu_element_atomic_getShape(VISU_ELEMENT_ATOMIC(iter.renderer)), radius);
      cairo_save(cr);
      cairo_scale(cr, radius, radius);
      cairo_set_source(cr, pat[iEle]);
      cairo_fill_preserve(cr);
      cairo_restore(cr);
      cairo_set_source_rgb(cr, 0.f, 0.f, 0.f);
      cairo_stroke(cr);
      cairo_restore(cr);
    }
}

static void svgDraw_axes(cairo_t *cr, VisuGlExtAxes *extAxes)
{
  float *rgb;
  VisuGlView *view;
  guint x, y;
  gfloat O[3], at[3], xyz[3];

  cairo_set_line_width(cr, (double)visu_gl_ext_lined_getWidth(VISU_GL_EXT_LINED(extAxes)));
  rgb = visu_gl_ext_lined_getRGBA(VISU_GL_EXT_LINED(extAxes));
  cairo_set_source_rgb(cr, rgb[0], rgb[1], rgb[2]);
  cairo_select_font_face(cr, "Serif", CAIRO_FONT_SLANT_NORMAL,
			 CAIRO_FONT_WEIGHT_NORMAL);
  cairo_set_font_size(cr, FONT_SIZE);

  view = visu_gl_ext_axes_getGlView(extAxes, &x, &y);
  /* We draw the 3 lines of the axes. */
  xyz[0] = xyz[1] = xyz[2] = 0.f;
  visu_gl_view_getWinCoordinates(view, O, xyz);
  cairo_move_to(cr, O[0] + x, O[1] + y);
  xyz[0] = 1.f;
  visu_gl_view_getWinCoordinates(view, at, xyz);
  cairo_line_to(cr, at[0] + x, at[1] + y);
  cairo_stroke(cr);
  xyz[0] = 1.1f;
  visu_gl_view_getWinCoordinates(view, at, xyz);
  cairo_move_to(cr, at[0] + x, at[1] + y);
  cairo_show_text(cr, visu_gl_ext_axes_getLabel(extAxes, TOOL_XYZ_X));
  xyz[0] = xyz[1] = xyz[2] = 0.f;
  cairo_move_to(cr, O[0] + x, O[1] + y);
  xyz[1] = 1.f;
  visu_gl_view_getWinCoordinates(view, at, xyz);
  cairo_line_to(cr, at[0] + x, at[1] + y);
  cairo_stroke(cr);
  xyz[1] = 1.1f;
  visu_gl_view_getWinCoordinates(view, at, xyz);
  cairo_move_to(cr, at[0] + x, at[1] + y);
  cairo_show_text(cr, visu_gl_ext_axes_getLabel(extAxes, TOOL_XYZ_Y));
  xyz[0] = xyz[1] = xyz[2] = 0.f;
  cairo_move_to(cr, O[0] + x, O[1] + y);
  xyz[2] = 1.f;
  visu_gl_view_getWinCoordinates(view, at, xyz);
  cairo_line_to(cr, at[0] + x, at[1] + y);
  cairo_stroke(cr);
  xyz[2] = 1.1f;
  visu_gl_view_getWinCoordinates(view, at, xyz);
  cairo_move_to(cr, at[0] + x, at[1] + y);
  cairo_show_text(cr, visu_gl_ext_axes_getLabel(extAxes, TOOL_XYZ_Z));

  g_object_unref(view);
}

static GList* svgSetup_pairs(VisuData *dataObj)
{
  GList *pairsLst;
  VisuPairLinkIter iter, *pairData;
  VisuPairSet *pairs;
  VisuPairSetIter it;
  gboolean valid;

  pairsLst = (GList*)0;

  pairs = visu_pair_set_new();
  visu_pair_set_setModel(pairs, dataObj);
  for (visu_pair_set_iter_new(pairs, &it, FALSE); it.link; visu_pair_set_iter_next(&it))
    {
      for (valid = visu_pair_link_iter_new(it.link, dataObj, &iter, FALSE, 0.15f); valid;
           valid = visu_pair_link_iter_next(&iter))
        {
          pairData = g_slice_alloc(sizeof(VisuPairLinkIter));
          *pairData = iter;
          pairsLst = g_list_prepend(pairsLst, pairData);
        }
    }
  g_object_unref(pairs);
  return pairsLst;
}

#define PAIRS_NCMPT 14
static GArray* svgCompute_pairs(const VisuGlExtPairs *pairs, const VisuGlView *view)
{
  int nPairs;
  GArray *glPairs;
  GList *tmpLst;
  VisuPairLinkIter *pairData;
  float u[3], radius, norm;
  ToolColor *color;
  float tmpPairs[PAIRS_NCMPT];
  GList *pairsLst;
  VisuElementRenderer *eleRenderer;
  const VisuNodeArrayRenderer *nodeRenderer = visu_gl_ext_pairs_getDataRenderer(pairs);
  VisuData *dataObj = VISU_DATA(visu_node_array_renderer_getNodeArray(nodeRenderer));
  VisuDataColorizer *colorizer;

  pairsLst = svgSetup_pairs(dataObj);
  nPairs = g_list_length(pairsLst);
  if (nPairs <= 0)
    return (GArray*)0;

  g_debug("Dump Cairo: found %d pairs to draw.", nPairs);
  glPairs = g_array_new(FALSE, FALSE, sizeof(gfloat));

  /* Render the list of pairs and free them each time. */
  colorizer = visu_node_array_renderer_getColorizer(nodeRenderer);
  for (tmpLst = pairsLst; tmpLst; tmpLst = g_list_next(tmpLst))
    {
      gfloat win[3];
      gfloat val;
      VisuPairLinkRenderer *renderer;

      pairData = (VisuPairLinkIter*)tmpLst->data;

      u[0] = pairData->xyz2[0] - pairData->xyz1[0];
      u[1] = pairData->xyz2[1] - pairData->xyz1[1];
      u[2] = pairData->xyz2[2] - pairData->xyz1[2];
      norm = sqrt(pairData->d2);
      u[0] /= norm;
      u[1] /= norm;
      u[2] /= norm;
      eleRenderer = visu_node_array_renderer_get(nodeRenderer, pairData->iter1.element);
      radius = visu_element_renderer_getExtent(eleRenderer);
      radius *= (colorizer) ? visu_data_colorizer_getScalingFactor(colorizer, dataObj, pairData->iter1.node) : 1.f;
      pairData->xyz1[0] += radius * u[0];
      pairData->xyz1[1] += radius * u[1];
      pairData->xyz1[2] += radius * u[2];
      eleRenderer = visu_node_array_renderer_get(nodeRenderer, pairData->iter2.element);
      radius = visu_element_renderer_getExtent(eleRenderer);
      radius *= (colorizer) ? visu_data_colorizer_getScalingFactor(colorizer, dataObj, pairData->iter2.node) : 1.f;
      pairData->xyz2[0] -= radius * u[0];
      pairData->xyz2[1] -= radius * u[1];
      pairData->xyz2[2] -= radius * u[2];
      visu_gl_view_getWinCoordinates(view, win, pairData->xyz1);
      g_array_append_vals(glPairs, win, 3);
      val = win[2] * 0.5f;
      visu_gl_view_getWinCoordinates(view, win, pairData->xyz2);
      g_array_append_vals(glPairs, win, 3);
      val += win[2] * 0.5f;
      g_array_append_val(glPairs, val);
      /* We save colour channel as passthrough. */
      color = visu_pair_link_getColor(pairData->parent);
      g_array_append_val(glPairs, pairData->coeff);
      g_array_append_vals(glPairs, color->rgba, 3);
      /* We save the width and the printLength in a passthrough. */
      renderer = visu_gl_ext_pairs_getLinkRenderer(pairs, pairData->parent);
      if (VISU_IS_PAIR_WIRE_RENDERER(renderer))
        val = visu_pair_wire_getWidth(VISU_PAIR_WIRE(pairData->parent));
      else
        val = 10.f;
      g_array_append_val(glPairs, val);
      if (visu_pair_link_getPrintLength(pairData->parent))
        val = norm;
      else
        val = -1.f;
      g_array_append_val(glPairs, val);
      val = visu_pair_wire_getStipple(VISU_PAIR_WIRE(pairData->parent));
      g_array_append_val(glPairs, val);
	  
      /* Free the data. */
      g_slice_free1(sizeof(VisuPairLinkIter), tmpLst->data);
    }

  /* Free the list itself. */
  g_list_free(pairsLst);

  if (glPairs->len)
    sort_by_z((float*)glPairs->data, tmpPairs, PAIRS_NCMPT, 6, 0, (glPairs->len - 1) / PAIRS_NCMPT);
  g_debug(" | will draw %d pairs.", glPairs->len / PAIRS_NCMPT);

  return glPairs;
}

#define NODES_NCMPT 12
static GArray* svgCompute_coordinates(const VisuNodeArrayRenderer *nodeRenderer,
                                      const VisuGlView *view)
{
  VisuDataIter iter;
  VisuElementRenderer *renderer;
  const ToolColor *color;
  GArray *coordinates;
  float tmpFloat[NODES_NCMPT];
  float radius, rgba[4];
  VisuData *dataObj = VISU_DATA(visu_node_array_renderer_getNodeArray(nodeRenderer));
  VisuDataColorizer *colorizer;
  ToolGlMatrix MV;

  colorizer = visu_node_array_renderer_getColorizer(nodeRenderer);

  /* We create a feedback mode to get node coordinates. */
  coordinates = g_array_new(FALSE, FALSE, sizeof(gfloat));

  /* First thing is to order nodes along z axes. */
  visu_gl_view_getModelView(view, &MV);
  for (visu_data_iter_new(dataObj, &iter, ITER_NODES_VISIBLE);
       visu_data_iter_isValid(&iter); visu_data_iter_next(&iter))
    {
      gfloat win[3], xyz[3];
      gfloat val;
      renderer = visu_node_array_renderer_get(nodeRenderer, iter.parent.element);
      color = visu_element_renderer_getColor(renderer);
      if (!colorizer || !visu_data_colorizer_getColor(colorizer, rgba, dataObj, iter.parent.node))
        {
          rgba[0] = color->rgba[0];
          rgba[1] = color->rgba[1];
          rgba[2] = color->rgba[2];
          rgba[3] = color->rgba[3];
        }
      /* We compute the node position in the eyes coordinates. */
      visu_gl_view_getWinCoordinates(view, win, iter.xyz);
      g_array_append_vals(coordinates, win, 3);
      /* We compute the node apparent radius using the real radius in
	 X direction in ModelView. */
      radius = visu_element_atomic_getRadius(VISU_ELEMENT_ATOMIC(renderer));
      radius *= (colorizer) ? visu_data_colorizer_getScalingFactor(colorizer, dataObj, iter.parent.node) : 1.f;
      xyz[0] = iter.xyz[0] + radius * MV.c[0][0];
      xyz[1] = iter.xyz[1] + radius * MV.c[1][0];
      xyz[2] = iter.xyz[2] + radius * MV.c[2][0];
      visu_gl_view_getWinCoordinates(view, win, xyz);
      g_array_append_vals(coordinates, win, 3);

      /* We store the number of the VisuElement. */
      val = iter.parent.iElement;
      g_array_append_val(coordinates, val);
      /* We store the element shape. */
      val = visu_element_atomic_getShape(VISU_ELEMENT_ATOMIC(renderer));
      g_array_append_val(coordinates, val);
      /* We store the node colour. */
      g_array_append_vals(coordinates, rgba, 4);
    }

  sort_by_z((gfloat*)coordinates->data, tmpFloat, NODES_NCMPT, 2, 0, (coordinates->len - 1) / NODES_NCMPT);

  return coordinates;
}

static cairo_pattern_t* svgGet_pattern(gboolean flat, const float rgba[4], float alpha)
{
  cairo_pattern_t *pat;
  float hsl[3], rgb[3], lum;
  if (flat)
    pat = cairo_pattern_create_rgba(rgba[0], rgba[1], rgba[2], rgba[3] * alpha);
  else
    {
      pat = cairo_pattern_create_radial(.4f, -.4f, .1f, 0.f, 0.f, 1.f);
      /* We get the Element colour in HSL. */
      tool_color_convertRGBtoHSL(hsl, rgba);
      lum = hsl[2];
      hsl[2] = CLAMP(lum + 0.2f, 0.f, 1.f);
      tool_color_convertHSLtoRGB(rgb, hsl);
      cairo_pattern_add_color_stop_rgba(pat, 0, rgb[0], rgb[1], rgb[2], alpha);
      hsl[2] = CLAMP(lum + 0.05f, 0.f, 1.f);
      tool_color_convertHSLtoRGB(rgb, hsl);
      cairo_pattern_add_color_stop_rgba(pat, 0.3, rgb[0], rgb[1], rgb[2], alpha);
      hsl[2] = CLAMP(lum - 0.05f, 0.f, 1.f);
      tool_color_convertHSLtoRGB(rgb, hsl);
      cairo_pattern_add_color_stop_rgba(pat, 0.7, rgb[0], rgb[1], rgb[2], alpha);
      hsl[2] = CLAMP(lum - 0.2f, 0.f, 1.f);
      tool_color_convertHSLtoRGB(rgb, hsl);
      cairo_pattern_add_color_stop_rgba(pat, 1, rgb[0], rgb[1], rgb[2], alpha);
    }
  return pat;
}
static cairo_pattern_t** svgSetup_patterns(VisuNodeArrayRenderer *array, gboolean flat)
{
  cairo_pattern_t **pat;
  VisuNodeArrayRendererIter iter;
  guint i;

  pat = g_malloc(sizeof(cairo_pattern_t*) * visu_node_array_getNElements(visu_node_array_renderer_getNodeArray(array), FALSE));
  for (visu_node_array_renderer_iter_new(array, &iter, FALSE), i = 0; iter.element;
       visu_node_array_renderer_iter_next(&iter), i++)
    pat[i] = svgGet_pattern(flat, visu_element_renderer_getColor(iter.renderer)->rgba, 1.f);
  return pat;
}

static GArray* svgCompute_box(const VisuBox *box, const VisuGlView *view)
{
  GArray *pts;
  gfloat v[8][3];

  pts = g_array_sized_new(FALSE, FALSE, sizeof(gfloat), 12 * 6);
  visu_box_getVertices(box, v, FALSE);

  visu_gl_view_getWinCoordinates(view, &g_array_index(pts, gfloat, 0), v[0]);
  visu_gl_view_getWinCoordinates(view, &g_array_index(pts, gfloat, 3), v[1]);
  visu_gl_view_getWinCoordinates(view, &g_array_index(pts, gfloat, 6), v[1]);
  visu_gl_view_getWinCoordinates(view, &g_array_index(pts, gfloat, 9), v[2]);
  visu_gl_view_getWinCoordinates(view, &g_array_index(pts, gfloat, 12), v[2]);
  visu_gl_view_getWinCoordinates(view, &g_array_index(pts, gfloat, 15), v[3]);
  visu_gl_view_getWinCoordinates(view, &g_array_index(pts, gfloat, 18), v[3]);
  visu_gl_view_getWinCoordinates(view, &g_array_index(pts, gfloat, 21), v[0]);

  visu_gl_view_getWinCoordinates(view, &g_array_index(pts, gfloat, 24), v[4]);
  visu_gl_view_getWinCoordinates(view, &g_array_index(pts, gfloat, 27), v[5]);
  visu_gl_view_getWinCoordinates(view, &g_array_index(pts, gfloat, 30), v[5]);
  visu_gl_view_getWinCoordinates(view, &g_array_index(pts, gfloat, 33), v[6]);
  visu_gl_view_getWinCoordinates(view, &g_array_index(pts, gfloat, 36), v[6]);
  visu_gl_view_getWinCoordinates(view, &g_array_index(pts, gfloat, 39), v[7]);
  visu_gl_view_getWinCoordinates(view, &g_array_index(pts, gfloat, 42), v[7]);
  visu_gl_view_getWinCoordinates(view, &g_array_index(pts, gfloat, 45), v[4]);

  visu_gl_view_getWinCoordinates(view, &g_array_index(pts, gfloat, 48), v[0]);
  visu_gl_view_getWinCoordinates(view, &g_array_index(pts, gfloat, 51), v[4]);
  visu_gl_view_getWinCoordinates(view, &g_array_index(pts, gfloat, 54), v[1]);
  visu_gl_view_getWinCoordinates(view, &g_array_index(pts, gfloat, 57), v[5]);
  visu_gl_view_getWinCoordinates(view, &g_array_index(pts, gfloat, 60), v[2]);
  visu_gl_view_getWinCoordinates(view, &g_array_index(pts, gfloat, 63), v[6]);
  visu_gl_view_getWinCoordinates(view, &g_array_index(pts, gfloat, 66), v[3]);
  visu_gl_view_getWinCoordinates(view, &g_array_index(pts, gfloat, 69), v[7]);

  g_array_set_size(pts, 72);

  return pts;
}
static gboolean svgDraw_line(cairo_t *cr, const gfloat xyz1[3],
                             const gfloat xyz2[3], float *rgb,
                             gboolean useFog, float fog[4], float se[2])
{
  float d[4], a;
  cairo_pattern_t *pat;
  double xm, ym; /* projection of surface centre on AB. */
  double xo, yo /* surface centre. */;
  double alpha, beta/* , lambda */;
  /* double xmin, xmax, ymin, ymax; */

  alpha = xyz2[0] - xyz1[0];
  beta  = xyz2[1] - xyz1[1];
  xo = g_width * 0.5;
  yo = g_height * 0.5;
  xm = alpha * alpha * xo - alpha * beta * (xyz1[1] - yo) + beta * beta * xyz1[0];
  ym = alpha * alpha * xyz1[1] - alpha * beta * (xyz1[0] - xo) + beta * beta * yo;
  xm /= alpha * alpha + beta * beta;
  ym /= alpha * alpha + beta * beta;
  /* lambda = (alpha * (xm - xyz1[0]) + beta * (ym - xyz1[1])) / (alpha * alpha + beta * beta); */

  /* xmin = g_width * (1. - 1. / zoomLevel) * 0.5; */
  /* xmax = g_width * (1. + 1. / zoomLevel) * 0.5; */
  /* ymin = g_height * (1. - 1. / zoomLevel) * 0.5; */
  /* ymax = g_height * (1. + 1. / zoomLevel) * 0.5; */

  /* The projection of AB is outside the box. */
  /* if (xm < xmin || xm > xmax || ym < ymin || ym > ymax) */
  /*   return FALSE; */
  /* The projection is outside AB and a and B are outside the box. */
  /* if ((lambda < 0. || lambda > 1.) && */
  /*     (xyz1[0] < xmin || xyz1[0] > xmax || xyz1[1] < ymin || xyz1[1] > ymax) && */
  /*     (xyz2[0] < xmin || xyz2[0] > xmax || xyz2[1] < ymin || xyz2[1] > ymax)) */
  /*   return FALSE; */

  pat = (cairo_pattern_t*)0;
  if (useFog)
    {
      if (xyz1[2] != xyz2[2])
	{
	  pat = cairo_pattern_create_linear(xyz1[0], xyz1[1], xyz2[0], xyz2[1]);
	  a = CLAMP((se[1] - xyz1[2]) / (se[1] - se[0]), 0.f, 1.f);
	  d[0] = a * rgb[0] + (1.f - a) * fog[0];
	  d[1] = a * rgb[1] + (1.f - a) * fog[1];
	  d[2] = a * rgb[2] + (1.f - a) * fog[2];
	  d[3] = a          + (1.f - a) * fog[3];
	  cairo_pattern_add_color_stop_rgba(pat, 0, d[0], d[1], d[2], d[3]);
	  a = CLAMP((se[1] - xyz2[2]) / (se[1] - se[0]), 0.f, 1.f);
	  d[0] = a * rgb[0] + (1.f - a) * fog[0];
	  d[1] = a * rgb[1] + (1.f - a) * fog[1];
	  d[2] = a * rgb[2] + (1.f - a) * fog[2];
	  d[3] = a          + (1.f - a) * fog[3];
	  cairo_pattern_add_color_stop_rgba(pat, 1, d[0], d[1], d[2], d[3]);
	  cairo_set_source(cr, pat);
	}
      else
	{
	  a = CLAMP((se[1] - xyz2[2]) / (se[1] - se[0]), 0.f, 1.f);
	  d[0] = a * rgb[0] + (1.f - a) * fog[0];
	  d[1] = a * rgb[1] + (1.f - a) * fog[1];
	  d[2] = a * rgb[2] + (1.f - a) * fog[2];
	  d[3] = a          + (1.f - a) * fog[3];
	  cairo_set_source_rgba(cr, d[0], d[1], d[2], d[3]);
	}	
    }
  else
    cairo_set_source_rgb(cr, rgb[0], rgb[1], rgb[2]);
  cairo_move_to(cr, xyz1[0], xyz1[1]);
  cairo_line_to(cr, xyz2[0], xyz2[1]);
  cairo_stroke(cr);
  if (pat)
    cairo_pattern_destroy(pat);

  return TRUE;
}
static void svgDraw_boxBack(cairo_t *cr, const GArray *box, gfloat val,
                            double width, float *rgb,
                            gboolean useFog, float fog[4], float se[2])
{
  guint i;

  cairo_set_line_width(cr, width);
  /* We draw the lines that have a boundary hidden by elements. */
  for (i = 0; i < box->len; i += 6)
    {
      const gfloat *xyz1 = &g_array_index(box, gfloat, i);
      const gfloat *xyz2 = &g_array_index(box, gfloat, i + 3);
      if (xyz1[2] >= val && xyz2[2] >= val)
        svgDraw_line(cr, xyz1, xyz2, rgb, useFog, fog, se);
    }
}
static void svgDraw_boxFront(cairo_t *cr, const GArray *box, gfloat val,
                             double width, float *rgb,
                             gboolean useFog, float fog[4], float se[2])
{
  guint i;

  cairo_set_line_width(cr, width);
  /* We draw the lines that have a boundary hidden by elements. */
  for (i = 0; i < box->len; i += 6)
    {
      const gfloat *xyz1 = &g_array_index(box, gfloat, i);
      const gfloat *xyz2 = &g_array_index(box, gfloat, i + 3);
      if (xyz1[2] < val || xyz2[2] < val)
        svgDraw_line(cr, xyz1, xyz2, rgb, useFog, fog, se);
    }
}

static void svgDraw_node(cairo_t *cr, VisuElementAtomicShapeId shape, float radius)
{
  if (shape == VISU_ELEMENT_ATOMIC_POINT)
    cairo_rectangle(cr, -radius / 4, -radius / 4, radius / 2, radius / 2);
  else
    cairo_arc(cr, 0.f, 0.f, radius, 0., 2 * G_PI);
}

static void svgDraw_nodesAndPairs(cairo_t *cr, const GArray *coordinates,
				  const GArray *pairs, cairo_pattern_t **pat,
				  gboolean flat, gboolean useFog,
                                  float rgbaFog[4], float se[2])
{
  guint iPairs;
  guint i;
  float alpha, radius, rgba[4];
  cairo_pattern_t *pat_;
  VisuElementAtomicShapeId shape;
  double xmin, xmax, ymin, ymax;

  cairo_set_line_width(cr, 1. / zoomLevel);
  cairo_set_font_size(cr, FONT_SMALL);
  iPairs = 0;
  alpha = 1.f;
  xmin = g_width * (1. - 1. / zoomLevel) * 0.5;
  xmax = g_width * (1. + 1. / zoomLevel) * 0.5;
  ymin = g_height * (1. - 1. / zoomLevel) * 0.5;
  ymax = g_height * (1. + 1. / zoomLevel) * 0.5;
  for (i = 0; i < coordinates->len; i+= NODES_NCMPT)
    {
      /* We draw the pairs in between. */
      svgDraw_pairs(cr, pairs, &iPairs, g_array_index(coordinates, gfloat, i + 2), useFog, rgbaFog, se);

      /* Compute the alpha for the fog. */
      alpha = (useFog) ?
        CLAMP((se[1] - g_array_index(coordinates, gfloat, i + 2)) / (se[1] - se[0]), 0.f, 1.f) : 1.f;
      
      radius = (g_array_index(coordinates, gfloat, i + 3) - g_array_index(coordinates, gfloat, i + 0)) *
	(g_array_index(coordinates, gfloat, i + 3) - g_array_index(coordinates, gfloat, i + 0)) +
	(g_array_index(coordinates, gfloat, i + 4) - g_array_index(coordinates, gfloat, i + 1)) *
	(g_array_index(coordinates, gfloat, i + 4) - g_array_index(coordinates, gfloat, i + 1));
      radius = sqrt(radius);

      g_debug("- %gx%g %g at %g", g_array_index(coordinates, gfloat, i + 0),
              g_array_index(coordinates, gfloat, i + 1),
              radius, g_array_index(coordinates, gfloat, i + 2));
      
      if (alpha > 0.f && radius > 0.f &&
          (g_array_index(coordinates, gfloat, i + 0) + radius) >= xmin &&
          (g_array_index(coordinates, gfloat, i + 0) - radius) <= xmax &&
          (g_array_index(coordinates, gfloat, i + 1) + radius) >= ymin &&
          (g_array_index(coordinates, gfloat, i + 1) - radius) <= ymax )
	{
          /* cairo_push_group(cr); */
          cairo_new_path(cr);
          cairo_save(cr);

          shape = (VisuElementAtomicShapeId)g_array_index(coordinates, gfloat, i + 7);
	  cairo_translate(cr, g_array_index(coordinates, gfloat, i + 0),
                          g_array_index(coordinates, gfloat, i + 1));
          svgDraw_node(cr, shape, radius);
	  cairo_save(cr);
	  cairo_scale(cr, radius, radius);
      
	  if (pat)
	    cairo_set_source(cr, pat[(int)g_array_index(coordinates, gfloat, i + 6)]);
	  else
	    {
              if (flat)
                {
                  rgba[0] = g_array_index(coordinates, gfloat, i + 8);
                  rgba[1] = g_array_index(coordinates, gfloat, i + 9);
                  rgba[2] = g_array_index(coordinates, gfloat, i + 10);
                  rgba[3] = g_array_index(coordinates, gfloat, i + 11);
                }
              else
                {
                  rgba[0] = g_array_index(coordinates, gfloat, i + 8);
                  rgba[1] = g_array_index(coordinates, gfloat, i + 9);
                  rgba[2] = g_array_index(coordinates, gfloat, i + 10);
                  rgba[3] = 1.f;
                }
	      pat_ = svgGet_pattern(flat, rgba, 1.f);
	      cairo_set_source(cr, pat_);
	      cairo_pattern_destroy(pat_);
	    }
	  cairo_fill_preserve(cr);
	  if (alpha < 1.f)
	    {
	      cairo_set_source_rgba(cr, rgbaFog[0], rgbaFog[1], rgbaFog[2],
				    1.f - alpha);
	      cairo_fill_preserve(cr);
            }
	  cairo_restore(cr);
	  cairo_set_source_rgb(cr, (1.f - alpha) * rgbaFog[0],
			       (1.f - alpha) * rgbaFog[1],
			       (1.f - alpha) * rgbaFog[2]);
          cairo_set_line_width(cr, 1. / zoomLevel);
	  cairo_stroke(cr);
          cairo_restore(cr);
          /* cairo_pop_group_to_source(cr); */
          /* cairo_paint_with_alpha (cr, alpha); */
	}
    }
  /* We draw the remaining pairs. */
  svgDraw_pairs(cr, pairs, &iPairs, -1, useFog, rgbaFog, se);
}
static int svgGet_stipple(guint16 stipple, double dashes[16])
{
  int n, i;
  gboolean status;

  n = 0;
  memset(dashes, '\0', sizeof(double) * 16);
  for (i = 0, status = (stipple & 1); i < 16; status = (stipple & (1 << i++)))
    {
      if (((stipple & (1 << i)) && !status) || (!(stipple & (1 << i)) && status))
        n += 1;
      dashes[n] += 1;
    }
  g_debug(" | %d -> ", stipple);
  for (i = 0; i <= n; i++)
    g_debug(" %f", dashes[i]);
  return n + 1;
}
static void svgDraw_pairs(cairo_t *cr, const GArray *pairs,
			  guint *iPairs, gfloat val,
                          gboolean useFog, float fog[4], float se[2])
{
  char distStr[8];
  double dashes[16];
  int n;

  if (pairs && *iPairs < pairs->len)
    {
      while (g_array_index(pairs, gfloat, *iPairs + 6) > val &&
             *iPairs < pairs->len)
	{
          n = svgGet_stipple((guint16)g_array_index(pairs, gfloat, *iPairs + 13), dashes);
	  g_debug(" | pair %d at (%f;%f) - (%f;%f) %g > %g,%g %d -> %d",
                  *iPairs / PAIRS_NCMPT,
                  g_array_index(pairs, gfloat, *iPairs + 0),
                  g_array_index(pairs, gfloat, *iPairs + 1),
                  g_array_index(pairs, gfloat, *iPairs + 3),
                  g_array_index(pairs, gfloat, *iPairs + 4),
                  g_array_index(pairs, gfloat, *iPairs + 6), val,
                  g_array_index(pairs, gfloat, *iPairs + 7),
                  (guint16)g_array_index(pairs, gfloat, *iPairs + 13), n);
          if (n > 1)
            cairo_set_dash(cr, dashes, n, 0);
	  cairo_set_line_width(cr, g_array_index(pairs, gfloat, *iPairs + 11) / zoomLevel);
	  if (svgDraw_line(cr, &g_array_index(pairs, gfloat, *iPairs + 0),
                           &g_array_index(pairs, gfloat, *iPairs + 3),
                           &g_array_index(pairs, gfloat, *iPairs + 8),
                           useFog, fog, se) &&
              g_array_index(pairs, gfloat, *iPairs + 12) > 0)
	    {
	      cairo_move_to(cr,
                            (g_array_index(pairs, gfloat, *iPairs + 0) + g_array_index(pairs, gfloat, *iPairs + 3)) * 0.5,
			    (g_array_index(pairs, gfloat, *iPairs + 1) + g_array_index(pairs, gfloat, *iPairs + 4)) * 0.5);
	      sprintf(distStr, "%7.3f", g_array_index(pairs, gfloat, *iPairs + 12));
	      cairo_show_text(cr, distStr);
	    }
          cairo_set_dash(cr, dashes, 0, 0);
	  *iPairs += PAIRS_NCMPT;
	}
      cairo_set_line_width(cr, 1. / zoomLevel);
    }
}
