/* emit.c
   Copyright (C) 2005,2006,2007 Eugene K. Ressler, Jr.

This file is part of Sketch, a small, simple system for making 
3d drawings with LaTeX and the PSTricks or TikZ package.

Sketch is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.

Sketch 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 the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Sketch; see the file COPYING.txt.  If not, see
http://www.gnu.org/copyleft */

#include <math.h>
#include "emit.h"
#include "scene.h"
#include "version.h"

// ---- emit output -----------------------------------------------------------

char standard_us_doc_template_file_name_flag[] =
  "<standard us doc template file name flag>";
char standard_euro_doc_template_file_name_flag[] =
  "<standard euro doc template file name flag>";

// concise floating point output
// idea of %g but with fixed rather than relative precision
// removing excessive 0's often reduces output file size dramatically
// and also eases reading
char *
flt_str_fmt (char *fmt, char *buf, double f)
{
  size_t i;

  sprintf (buf, fmt, f);

  // trim off useless zeros and decimals
  for (i = strlen (buf); i > 0 && buf[i - 1] == '0'; i--)
    /* skip */ ;
  if (i > 0 && buf[i - 1] == '.')
    i--;
  buf[i] = '\0';

  // remove leading zeros before decimal
  if (buf[0] == '0' && buf[1] == '.')
    for (i = 0; (buf[i] = buf[i + 1]) != '\0'; i++)
      /* skip */ ;
  else if (buf[0] == '-' && buf[1] == '0' && buf[2] == '.')
    for (i = 1; (buf[i] = buf[i + 1]) != '\0'; i++)
      /* skip */ ;

  // fix -0
  if (strcmp (buf, "-0") == 0)
    strcpy (buf, "0");

  return buf;
}

char *
flt_str (char *buf, double f)
{
  return flt_str_fmt ("%.3f", buf, f);
}

// scan and return all the legal forms of special arg: ints, int range, 
// *arg_len is set to number of chars consumed by scanning even if there are range errors
// return value is number of good arg indices scanned into *arg_index_1|2
static int
scan_special_arg (SPECIAL_OBJECT * special, int i,	// start index
		  int *arg_len,	// chars scanned
		  int *arg_index_1,	// arg array indices (# - 1)
		  int *arg_index_2, SRC_LINE line)	// line # for error messages
{
  int i1, i2, len, n_args, n_errs;

  // try two-arg cases and then one arg and then assume zero
  if (sscanf (&special->code[i], "%d-%d%n", &i1, &i2, &len) >= 2 ||
      sscanf (&special->code[i], "{%d-%d}%n", &i1, &i2, &len) >= 2)
    {
      *arg_len = len;
      *arg_index_1 = i1 - 1;
      *arg_index_2 = i2 - 1;
      n_args = 2;
    }
  else if (sscanf (&special->code[i], "%d%n", &i1, &len) >= 1 ||
	   sscanf (&special->code[i], "{%d}%n", &i1, &len) >= 1)
    {
      *arg_len = len;
      *arg_index_1 = i1 - 1;
      n_args = 1;
    }
  else
    {
      *arg_len = 0;
      n_args = 0;
    }
  n_errs = 0;
  if (n_args >= 1 && (i1 < 1 || i1 > special->pts->n_pts))
    {
      err (line, "special arg #%d: out of range #[1-%d]", i1,
	   special->pts->n_pts);
      ++n_errs;
    }
  if (n_args >= 2 && (i2 < 1 || i2 > special->pts->n_pts))
    {
      err (line, "special arg #n-%d: out of range #[1-%d]", i2,
	   special->pts->n_pts);
      ++n_errs;
    }
  return n_errs > 0 ? 0 : n_args;
}

// TikZ only does integer angles
char *
fmt_angle_tikz (char *buf, double theta, SRC_LINE line)
{
  int i_theta = (int) ((theta >= 0) ? (theta + 0.5) : (theta - 0.5));
  double err = theta - i_theta;
  if (fabs (err) >= 0.1)
    warn (line, "TikZ angle rounding error is %.2 degrees", err);
  return flt_str_fmt ("%1.f", buf, theta);
}

char *
fmt_angle_pst (char *buf, double theta, SRC_LINE line)
{
  return flt_str (buf, theta);
}

typedef char *(*FMT_ANGLE_FUNC) (char *buf, double theta, SRC_LINE line);

FMT_ANGLE_FUNC fmt_angle_tbl[] = {
  fmt_angle_pst,
  fmt_angle_tikz,
  fmt_angle_pst,
  fmt_angle_tikz,
};

// this parses, substitues, notes any errors, and (if f is set) prints special output
// so it's used both for syntax checking during input and to generate output
void
process_special (FILE * f, SPECIAL_OBJECT * special, SRC_LINE line)
{
  char ch, buf1[16], buf2[16];
  int i_arg_prev, i_arg, arg_len, arg_index_1, arg_index_2;

  i_arg_prev = i_arg = 0;
  while ((ch = special->code[i_arg]) != '\0')
    {
      if (ch == '#')
	{
	  if (special->code[i_arg + 1] == '#')
	    {
	      if (f)
		fprintf (f, "%.*s#",
			 i_arg - i_arg_prev, &special->code[i_arg_prev]);
	      arg_len = 1;
	    }
	  else
	    {
	      switch (scan_special_arg
		      (special, i_arg + 1, &arg_len, &arg_index_1,
		       &arg_index_2, line))
		{
		case 2:
		  if (f)
		    fprintf (f, "%.*s{%s}", i_arg - i_arg_prev,	// number of chars to write
			     &special->code[i_arg_prev],	// start of chars
			     (*fmt_angle_tbl
			      [global_env->output_language])
			     (buf1,
			      180 / PI *
			      atan2 (special->pts->v[arg_index_2][Y] -
				     special->pts->v[arg_index_1][Y],
				     special->pts->v[arg_index_2][X] -
				     special->pts->v[arg_index_1][X]), line));
		  break;
		case 1:
		  if (f)
		    fprintf (f, "%.*s(%s,%s)", i_arg - i_arg_prev,	// number of chars to write
			     &special->code[i_arg_prev],	// start of chars
			     flt_str (buf1,
				      special->pts->v[arg_index_1][X]),
			     flt_str (buf2, special->pts->v[arg_index_1][Y]));
		  break;
		case 0:
		  if (arg_len == 0)
		    {		// couldn't scan an index at all
		      if (f)
			fprintf (f, "%.*s#", i_arg - i_arg_prev,
				 &special->code[i_arg_prev]);
		      warn (line,
			    "use of '#' not as special arg (try ##)",
			    arg_len, &special->code[i_arg]);
		    }
		  break;
		}
	    }
	  i_arg += (arg_len + 1);
	  i_arg_prev = i_arg;
	}
      else
	{
	  ++i_arg;
	}
    }
  // print out the last stretch of code
  if (f)
    fprintf (f, "%s\n", &special->code[i_arg_prev]);
}

static void
emit_points_pst (FILE * f, POINT_LIST_3D * pts)
{
  int i;
  char buf1[16], buf2[16];

  for (i = 0; i < pts->n_pts; i++)
    fprintf (f, "(%s,%s)",
	     flt_str (buf1, pts->v[i][X]), flt_str (buf2, pts->v[i][Y]));
}

static void
emit_dots_pst (FILE * f, OBJECT * obj)
{
  DOTS_OBJECT *dots = (DOTS_OBJECT *) obj;
  fprintf (f, "\\psdots");
  emit_opts (f, dots->opts, global_env->output_language);
  emit_points_pst (f, dots->pts);
  fprintf (f, "\n");
}

static void
emit_line_pst (FILE * f, OBJECT * obj)
{
  LINE_OBJECT *line = (LINE_OBJECT *) obj;
  fprintf (f, "\\psline");
  emit_opts (f, line->opts, global_env->output_language);
  emit_points_pst (f, line->pts);
  fprintf (f, "\n");
}

static void
emit_curve_pst (FILE * f, OBJECT * obj)
{
  CURVE_OBJECT *curve = (CURVE_OBJECT *) obj;
  fprintf (f, "\\pscurve");
  emit_opts (f, curve->opts, global_env->output_language);
  emit_points_pst (f, curve->pts);
  fprintf (f, "\n");
}

static void
emit_polygon_pst (FILE * f, OBJECT * obj)
{
  POLYGON_OBJECT *poly = (POLYGON_OBJECT *) obj;
  fprintf (f, "\\pspolygon");
  emit_opts (f, poly->opts, global_env->output_language);
  emit_points_pst (f, poly->pts);
  fprintf (f, "\n");
}

static void
emit_special_pst (FILE * f, OBJECT * obj)
{
  process_special (f, (SPECIAL_OBJECT *) obj, no_line);
}

typedef void (*EMIT_FUNC) (FILE * f, OBJECT *);

static EMIT_FUNC emit_tbl_pst[] = {
  NULL,				// O_BASE
  NULL,				// O_TAG_DEF
  NULL,				// O_OPTS_DEF
  NULL,				// O_SCALAR_DEF
  NULL,				// O_POINT_DEF
  NULL,				// O_VECTOR_DEF
  NULL,				// O_TRANSFORM_DEF
  emit_dots_pst,
  emit_line_pst,
  emit_curve_pst,
  emit_polygon_pst,
  emit_special_pst,
  NULL,				// O_SWEEP (flattened)
  NULL,				// O_REPEAT (flattened)
  NULL,				// O_COMPOUND (flattened)
};

static void
emit_points_tkz (FILE * f, POINT_LIST_3D * pts, char *twixt, char *final)
{
  int i;
  char buf1[16], buf2[16];

  for (i = 0; i < pts->n_pts; i++)
    fprintf (f, "(%s,%s)%s",
	     flt_str (buf1, pts->v[i][X]),
	     flt_str (buf2, pts->v[i][Y]),
	     (i == pts->n_pts - 1) ? final : twixt);
}

static void
emit_dots_tkz (FILE * f, OBJECT * obj)
{
  static char *skip[] = { "dotsize", NULL };
  char *dotsize, *cmd;

  DOTS_OBJECT *dots = (DOTS_OBJECT *) obj;

  // An ugly hack because TikZ uses special syntax for circles...
  dotsize = opt_val(dots->opts, "dotsize");
  if (dotsize == NULL)
    dotsize = "2pt";
  cmd = safe_malloc(strlen(dotsize) + 100);
  sprintf(cmd, " circle (%s)", dotsize);

  fprintf (f, "\\filldraw");
  emit_opts_with_exceptions (f, dots->opts, skip, global_env->output_language);
  emit_points_tkz (f, dots->pts, cmd, cmd);
  fprintf (f, ";\n");

  safe_free(cmd);
}

static void
emit_line_tkz (FILE * f, OBJECT * obj)
{
  LINE_OBJECT *line = (LINE_OBJECT *) obj;
  fprintf (f, "\\draw");
  emit_opts (f, line->opts, global_env->output_language);
  emit_points_tkz (f, line->pts, "--", "");
  fprintf (f, ";\n");
}

static void
emit_curve_tkz (FILE * f, OBJECT * obj)
{
  CURVE_OBJECT *curve = (CURVE_OBJECT *) obj;
  fprintf (f, "\\curve");
  emit_opts (f, curve->opts, global_env->output_language);
  emit_points_tkz (f, curve->pts, "--", "");
  fprintf (f, ";\n");
}

static void
emit_polygon_tkz (FILE * f, OBJECT * obj)
{
  POLYGON_OBJECT *poly = (POLYGON_OBJECT *) obj;
  fprintf (f, "\\filldraw");
  emit_opts (f, poly->opts, global_env->output_language);
  emit_points_tkz (f, poly->pts, "--", "--cycle");
  fprintf (f, ";\n");
}

static void
emit_special_tkz (FILE * f, OBJECT * obj)
{
  process_special (f, (SPECIAL_OBJECT *) obj, no_line);
}

static EMIT_FUNC emit_tbl_tkz[] = {
  NULL,				// O_BASE
  NULL,				// O_TAG_DEF
  NULL,				// O_OPTS_DEF
  NULL,				// O_SCALAR_DEF
  NULL,				// O_POINT_DEF
  NULL,				// O_VECTOR_DEF
  NULL,				// O_TRANSFORM_DEF
  emit_dots_tkz,
  emit_line_tkz,
  emit_curve_tkz,
  emit_polygon_tkz,
  emit_special_tkz,
  NULL,				// O_SWEEP (flattened)
  NULL,				// O_REPEAT (flattened)
  NULL,				// O_COMPOUND (flattened)
};

static EMIT_FUNC *emit_tbl_tbl[] = {
  emit_tbl_pst,
  emit_tbl_tkz,
  emit_tbl_pst,
  emit_tbl_tkz,
};

#define DOC_TEMPLATE_ESCAPE_STRING "%%SKETCH_OUTPUT%%"
#define DOC_TEMPLATE_ESCAPE_STRING_LEN (sizeof(DOC_TEMPLATE_ESCAPE_STRING) - 1)

char standard_us_doc_template_tikz_latex[] =
  "\\documentclass[letterpaper,12pt]{article}\n"
  "\\usepackage[x11names,rgb]{xcolor}\n"
  "\\usepackage{tikz}\n"
  "\\usetikzlibrary{snakes}\n"
  "\\usetikzlibrary{arrows}\n"
  "\\usetikzlibrary{shapes}\n"
  "\\usetikzlibrary{backgrounds}\n"
  "\\usepackage{amsmath}\n"
  "\\oddsidemargin 0in\n"
  "\\evensidemargin 0in\n"
  "\\topmargin 0in\n"
  "\\headheight 0in\n"
  "\\headsep 0in\n"
  "\\textheight 9in\n"
  "\\textwidth 6.5in\n"
  "\\begin{document}\n"
  "\\pagestyle{empty}\n"
  "\\vspace*{\\fill}\n"
  "\\begin{center}\n"
  DOC_TEMPLATE_ESCAPE_STRING "\n"
  "\\end{center}\n" "\\vspace*{\\fill}\n" "\\end{document}\n";

char standard_euro_doc_template_tikz_latex[] =
  "\\documentclass[a4paper,12pt]{article}\n"
  "\\usepackage[x11names,rgb]{xcolor}\n"
  "\\usepackage{tikz}\n"
  "\\usetikzlibrary{snakes}\n"
  "\\usetikzlibrary{arrows}\n"
  "\\usetikzlibrary{shapes}\n"
  "\\usetikzlibrary{backgrounds}\n"
  "\\usepackage{amsmath}\n"
  "\\oddsidemargin -10mm\n"
  "\\evensidemargin -10mm\n"
  "\\topmargin 5mm\n"
  "\\headheight 0cm\n"
  "\\headsep 0cm\n"
  "\\textheight 247mm\n"
  "\\textwidth 160mm\n"
  "\\begin{document}\n"
  "\\pagestyle{empty}\n"
  "\\vspace*{\\fill}\n"
  "\\begin{center}\n"
  DOC_TEMPLATE_ESCAPE_STRING "\n"
  "\\end{center}\n" "\\vspace*{\\fill}\n" "\\end{document}\n";

char standard_us_doc_template_pst_latex[] =
  "\\documentclass[letterpaper,12pt]{article}\n"
  "\\usepackage{amsmath}\n"
  "\\usepackage{pstricks}\n"
  "\\usepackage{pstricks-add}\n"
  "\\oddsidemargin 0in\n"
  "\\evensidemargin 0in\n"
  "\\topmargin 0in\n"
  "\\headheight 0in\n"
  "\\headsep 0in\n"
  "\\textheight 9in\n"
  "\\textwidth 6.5in\n"
  "\\begin{document}\n"
  "\\pagestyle{empty}\n"
  "\\vspace*{\\fill}\n"
  "\\begin{center}\n"
  DOC_TEMPLATE_ESCAPE_STRING "\n"
  "\\end{center}\n" "\\vspace*{\\fill}\n" "\\end{document}\n";

char standard_euro_doc_template_pst_latex[] =
  "\\documentclass[a4paper,12pt]{article}\n"
  "\\usepackage{amsmath}\n"
  "\\usepackage{pstricks}\n"
  "\\usepackage{pstricks-add}\n"
  "\\oddsidemargin -10mm\n"
  "\\evensidemargin -10mm\n"
  "\\topmargin 5mm\n"
  "\\headheight 0cm\n"
  "\\headsep 0cm\n"
  "\\textheight 247mm\n"
  "\\textwidth 160mm\n"
  "\\begin{document}\n"
  "\\pagestyle{empty}\n"
  "\\vspace*{\\fill}\n"
  "\\begin{center}\n"
  DOC_TEMPLATE_ESCAPE_STRING "\n"
  "\\end{center}\n" "\\vspace*{\\fill}\n" "\\end{document}\n";

/* ---------------------------------------------------------------------- */

char standard_us_doc_template_tikz_context[] =
  "\\usemodule[tikz] \\usetikzlibrary[snakes,arrows,shapes,backgrounds]\n"
  "\\setuppapersize[letter][letter]\n"
  "\\setuplayout[topspace=0in,backspace=0in,header=0in,footer=0in,height=middle,width=middle]\n"
  "\\setuppagenumbering[state=stop] % no page numbers\n"
  "\\starttext\n"
  "\\startalignment[middle]\n"
  DOC_TEMPLATE_ESCAPE_STRING "\n"
  "\\stopalignment\n"
  "\\stoptext\n";

char standard_euro_doc_template_tikz_context[] =
  "\\usemodule[tikz] \\usetikzlibrary[snakes,arrows,shapes,backgrounds]\n"
  "\\setuppapersize[a4][a4]\n"
  "\\setuplayout[topspace=0cm,backspace=0cm,header=0cm,footer=0cm,height=middle,width=middle]\n"
  "\\setuppagenumbering[state=stop] % no page numbers\n"
  "\\starttext\n"
  "\\startalignment[middle]\n"
  DOC_TEMPLATE_ESCAPE_STRING "\n"
  "\\stopalignment\n"
  "\\stoptext\n";

char standard_us_doc_template_pst_context[] =
  "PSTricks does not work with ConTeXt as of 1 Feb 2008.\n";

char standard_euro_doc_template_pst_context[] =
  "PSTricks does not work with ConTeXt as of 1 Feb 2008.\n";

char *standard_us_doc_template[] = {
  standard_us_doc_template_pst_latex,
  standard_us_doc_template_tikz_latex,
  standard_us_doc_template_pst_context,
  standard_us_doc_template_tikz_context,
};

char *standard_euro_doc_template[] = {
  standard_euro_doc_template_pst_latex,
  standard_euro_doc_template_tikz_latex,
  standard_euro_doc_template_pst_context,
  standard_euro_doc_template_tikz_context,
};

char *
read_file_as_string (FILE * f)
{
  size_t len = 0;
  int buf_size = 1024;
  char *buf = safe_malloc (buf_size + 1);
  for (;;)
    {
      len += fread (buf + len, 1, buf_size - len, f);
      if (feof (f) || ferror (f))
	{
	  buf[len] = '\0';
	  return buf;
	}
      buf_size *= 2;
      buf = safe_realloc (buf, buf_size + 1);
    }
}

char *
doc_template_from_file (char *file_name, int output_language)
{
  FILE *f;
  char *r;

  if (file_name == NULL)
    return NULL;
  if (file_name == standard_us_doc_template_file_name_flag)
    return safe_strdup (standard_us_doc_template[output_language]);
  if (file_name == standard_euro_doc_template_file_name_flag)
    return safe_strdup (standard_euro_doc_template[output_language]);

  f = fopen (file_name, "r");
  if (!f)
    {
      err (no_line, "can't open document template '%s%' for input\n",
	   file_name);
      return safe_strdup (standard_us_doc_template_pst_latex);
    }
  r = read_file_as_string (f);
  fclose (f);
  return r;
}

void
emit_preamble_pst_latex (FILE * f, BOX_3D * ext, GLOBAL_ENV * env)
{
  char buf1[16], buf2[16], buf3[16], buf4[16];

  if (global_env_is_set_p (env, GE_OPTS))
    {
      fprintf (f, "\\psset{");
      emit_opts_raw (f, env->opts, global_env->output_language);
      fprintf (f, "}\n");
    }

  if (global_env_is_set_p (env, GE_FRAME))
    {
      if (env->frame_opts)
	fprintf (f, "\\psframebox[%s]{", env->frame_opts);
      else
	fprintf (f, "\\psframebox[framesep=0pt]{");
    }

  fprintf (f, "\\begin{pspicture%s}",
	   global_env_is_set_p (env, GE_EXTENT) ? "*" : "");

  if (global_env_is_set_p (env, GE_BASELINE))
    fprintf (f, "[%s]", flt_str (buf1, env->baseline));

  fprintf (f,
	   "(%s,%s)(%s,%s)\n",
	   flt_str (buf1, ext->min[X]),
	   flt_str (buf2, ext->min[Y]),
	   flt_str (buf3, ext->max[X]), flt_str (buf4, ext->max[Y]));

  if (cmp_with_global_pst_version(env, STRINGIFY(PST_LINEJOIN_VERSION), no_line) < 0) 
    {
      // old way to set linejoin
      fprintf (f, "\\pstVerb{1 setlinejoin}\n");
    }
  else
    {
      fprintf (f, 
	       "%% If your PSTricks is earlier than Version "
	       STRINGIFY(PST_LINEJOIN_VERSION) ", it will fail here.\n"
	       "%% Use sketch -V option for backward compatibility.\n"
	       "\\psset{linejoin=1}\n");
    }
}

void
emit_preamble_tkz_latex (FILE * f, BOX_3D * ext, GLOBAL_ENV * env)
{
  char buf1[16], buf2[16], buf3[16], buf4[16];
  int picture_opts_p = 0;

  if (global_env_is_set_p (env, GE_FRAME))
    {
      if (env->frame_opts)
	warn (no_line, "frame options [%s] ignored (TikZ)", env->frame_opts);
      else
	{
	  fprintf (f, "{\\fboxsep=0pt\\fbox{");
	  warn (no_line,
		"remove frame around TikZ/PGF pictures for debugging");
	}
    }

  fprintf (f, "\\begin{tikzpicture}[join=round");
  if (global_env_is_set_p (env, GE_OPTS))
    {
      fprintf (f, ",");
      emit_opts_raw (f, env->opts, global_env->output_language);
    }
  if (global_env_is_set_p (env, GE_BASELINE))
    {
      fprintf (f, ",");
      fprintf (f, "baseline=%s", flt_str (buf1, env->baseline));
    }
  fprintf (f, "]\n");
  if (global_env_is_set_p (env, GE_EXTENT))
    {
      flt_str (buf1, ext->min[X]);
      flt_str (buf2, ext->min[Y]);
      flt_str (buf3, ext->max[X]);
      flt_str (buf4, ext->max[Y]);
      fprintf (f,
	       "\\useasboundingbox(%s,%s) rectangle (%s,%s);\n"
	       "\\clip(%s,%s) rectangle (%s,%s);\n",
	       buf1, buf2, buf3, buf4, buf1, buf2, buf3, buf4);
    }
}

// -----------------------------------------------------------------

void
emit_preamble_pst_context (FILE * f, BOX_3D * ext, GLOBAL_ENV * env)
{
  char buf1[16], buf2[16], buf3[16], buf4[16];

  if (global_env_is_set_p (env, GE_OPTS))
    {
      fprintf (f, "\\psset{");
      emit_opts_raw (f, env->opts, global_env->output_language);
      fprintf (f, "}\n");
    }

  fprintf (f, 
	   "%% ConTeXt does not yet support PSTricks.\n"
	   "%% This is a guess at what the syntax might be.\n");

  if (global_env_is_set_p (env, GE_FRAME))
    {
      if (env->frame_opts)
	fprintf (f, "\\psframebox[%s]{", env->frame_opts);
      else
	fprintf (f, "\\psframebox[framesep=0pt]{");
    }

  fprintf (f, "\\startpspicture%s",
	   global_env_is_set_p (env, GE_EXTENT) ? "*" : "");

  if (global_env_is_set_p (env, GE_BASELINE))
    fprintf (f, "[%s]", flt_str (buf1, env->baseline));

  fprintf (f,
	   "(%s,%s)(%s,%s)\n",
	   flt_str (buf1, ext->min[X]),
	   flt_str (buf2, ext->min[Y]),
	   flt_str (buf3, ext->max[X]), flt_str (buf4, ext->max[Y]));

  fprintf (f, "\\pstVerb{1 setlinejoin}\n");
}

void
emit_preamble_tkz_context (FILE * f, BOX_3D * ext, GLOBAL_ENV * env)
{
  char buf1[16], buf2[16], buf3[16], buf4[16];
  int picture_opts_p = 0;

  if (global_env_is_set_p (env, GE_FRAME))
    {
      if (env->frame_opts)
	warn (no_line, "frame options [%s] ignored (TikZ)", env->frame_opts);
      else
	{
	  fprintf (f, "{\\fboxsep=0pt\\fbox{");
	  warn (no_line,
		"remove frame around TikZ/PGF pictures for debugging");
	}
    }

  fprintf (f, "\\starttikzpicture[join=round");
  if (global_env_is_set_p (env, GE_OPTS))
    {
      fprintf (f, ",");
      emit_opts_raw (f, env->opts, global_env->output_language);
    }
  if (global_env_is_set_p (env, GE_BASELINE))
    {
      fprintf (f, ",");
      fprintf (f, "baseline=%s", flt_str (buf1, env->baseline));
    }
  fprintf (f, "]\n");
  if (global_env_is_set_p (env, GE_EXTENT))
    {
      flt_str (buf1, ext->min[X]);
      flt_str (buf2, ext->min[Y]);
      flt_str (buf3, ext->max[X]);
      flt_str (buf4, ext->max[Y]);
      fprintf (f,
	       "\\useasboundingbox(%s,%s) rectangle (%s,%s);\n"
	       "\\clip(%s,%s) rectangle (%s,%s);\n",
	       buf1, buf2, buf3, buf4, buf1, buf2, buf3, buf4);
    }
}

typedef void (*EMIT_PREAMBLE_FUNC) (FILE * f, BOX_3D * ext, GLOBAL_ENV * env);

EMIT_PREAMBLE_FUNC emit_preamble_tbl[] = {
  emit_preamble_pst_latex,
  emit_preamble_tkz_latex,
  emit_preamble_pst_context,
  emit_preamble_tkz_context,
};

void
emit_postamble_pst_latex (FILE * f, GLOBAL_ENV * env)
{
  fprintf (f, "\\end{pspicture%s}",
	   global_env_is_set_p (env, GE_EXTENT) ? "*" : "");
  if (global_env_is_set_p (env, GE_FRAME))
    fprintf (f, "}");
}

void
emit_postamble_tkz_latex (FILE * f, GLOBAL_ENV * env)
{
  fprintf (f, "\\end{tikzpicture}");
  if (global_env_is_set_p (env, GE_FRAME))
    fprintf (f, "}}");
}

void
emit_postamble_pst_context (FILE * f, GLOBAL_ENV * env)
{
  fprintf (f, "\\stoppspicture%s}",
	   global_env_is_set_p (env, GE_EXTENT) ? "*" : "");
  if (global_env_is_set_p (env, GE_FRAME))
    fprintf (f, "}");
}

void
emit_postamble_tkz_context (FILE * f, GLOBAL_ENV * env)
{
  fprintf (f, "\\stoptikzpicture");
  if (global_env_is_set_p (env, GE_FRAME))
    fprintf (f, "}}");
}

typedef void (*EMIT_POSTAMBLE_FUNC) (FILE * f, GLOBAL_ENV * env);

EMIT_POSTAMBLE_FUNC emit_postamble_tbl[] = {
  emit_postamble_pst_latex,
  emit_postamble_tkz_latex,  
  emit_postamble_pst_context,
  emit_postamble_tkz_context,
};

void
emit (FILE * f, OBJECT * obj, GLOBAL_ENV * env, char *doc_template_file_name)
{
  BOX_3D ext[1];
  int n_obj;
  OBJECT *p;
  char buf1[16], buf2[16], buf3[16], buf4[16];
  char *escape, *doc_template;

  doc_template =
    doc_template_from_file (doc_template_file_name, env->output_language);

  get_extent (obj, ext, &n_obj);
  if (n_obj == 0)
    err (no_line, "no objects to write");
  else
    {

      remark (no_line, "scene bb=(%s,%s)(%s,%s)",
	      flt_str (buf1, ext->min[X]),
	      flt_str (buf2, ext->min[Y]),
	      flt_str (buf3, ext->max[X]), flt_str (buf4, ext->max[Y]));

      if (get_transformed_global_env_extent (ext, env))
	{
	  remark (no_line, "actual bb=(%s,%s)(%s,%s)",
		  flt_str (buf1, ext->min[X]),
		  flt_str (buf2, ext->min[Y]),
		  flt_str (buf3, ext->max[X]), flt_str (buf4, ext->max[Y]));
	}

      remark (no_line, "writing %d objects", n_obj);

      fprintf (f,
	       "%% Sketch output, version " VER_STRING "\n"
	       "%% Output language: %s\n",
	       output_language_str[env->output_language]);
      escape = NULL;
      if (doc_template)
	{
	  escape = strstr (doc_template, DOC_TEMPLATE_ESCAPE_STRING);
	  if (escape)
	    fprintf (f, "%.*s", escape - doc_template, doc_template);
	  else
	    warn (no_line,
		  "document template with no escape '%s' has been ignored",
		  DOC_TEMPLATE_ESCAPE_STRING);
	}

      (*emit_preamble_tbl[env->output_language]) (f, ext, env);

      for (p = obj; p; p = p->sibling)
	{
	  if (emit_tbl_tbl[global_env->output_language][p->tag] == NULL)
	    die (no_line, "emit: bad tag %d", p->tag);
	  if (xy_overlap_p (p, ext))
	    (*emit_tbl_tbl[global_env->output_language][p->tag]) (f, p);
	}

      (*emit_postamble_tbl[env->output_language]) (f, env);

      if (escape)
	{
	  escape += DOC_TEMPLATE_ESCAPE_STRING_LEN;
	  fprintf (f, "%s", escape);
	  if (strstr (escape, DOC_TEMPLATE_ESCAPE_STRING))
	    warn (no_line,
		  "more than one escape in document template; all but first ignored");
	}
      fprintf (f, "%% End sketch output\n");
    }
}
