/* 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");
   }
}