1/*
2 * "$Id: print-ps.c,v 1.104 2011/03/04 13:05:08 rlk Exp $"
3 *
4 *   Print plug-in Adobe PostScript driver for the GIMP.
5 *
6 *   Copyright 1997-2002 Michael Sweet (mike@easysw.com) and
7 *	Robert Krawitz (rlk@alum.mit.edu)
8 *
9 *   This program is free software; you can redistribute it and/or modify it
10 *   under the terms of the GNU General Public License as published by the Free
11 *   Software Foundation; either version 2 of the License, or (at your option)
12 *   any later version.
13 *
14 *   This program is distributed in the hope that it will be useful, but
15 *   WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 *   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
17 *   for more details.
18 *
19 *   You should have received a copy of the GNU General Public License
20 *   along with this program; if not, write to the Free Software
21 *   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22 */
23
24/*
25 * This file must include only standard C header files.  The core code must
26 * compile on generic platforms that don't support glib, gimp, gtk, etc.
27 */
28
29#ifdef HAVE_CONFIG_H
30#include <config.h>
31#endif
32#include <gutenprint/gutenprint.h>
33#include <gutenprint/gutenprint-intl-internal.h>
34#include "gutenprint-internal.h"
35#include <time.h>
36#include <string.h>
37#include <math.h>
38#ifdef HAVE_LIMITS_H
39#include <limits.h>
40#endif
41#include <stdio.h>
42#include <unistd.h>
43#include "xmlppd.h"
44
45#ifdef _MSC_VER
46#define strncasecmp(s,t,n) _strnicmp(s,t,n)
47#define strcasecmp(s,t) _stricmp(s,t)
48#endif
49
50/*
51 * Local variables...
52 */
53
54static char *m_ppd_file = NULL;
55static stp_mxml_node_t *m_ppd = NULL;
56
57
58/*
59 * Local functions...
60 */
61
62static void	ps_hex(const stp_vars_t *, unsigned short *, int);
63static void	ps_ascii85(const stp_vars_t *, unsigned short *, int, int);
64
65static const stp_parameter_t the_parameters[] =
66{
67  {
68    "PPDFile", N_("PPDFile"), "Color=Yes,Category=Basic Printer Setup",
69    N_("PPD File"),
70    STP_PARAMETER_TYPE_FILE, STP_PARAMETER_CLASS_FEATURE,
71    STP_PARAMETER_LEVEL_BASIC, 1, 1, STP_CHANNEL_NONE, 1, 0
72  },
73  {
74    "PageSize", N_("Page Size"), "Color=No,Category=Basic Printer Setup",
75    N_("Size of the paper being printed to"),
76    STP_PARAMETER_TYPE_STRING_LIST, STP_PARAMETER_CLASS_CORE,
77    STP_PARAMETER_LEVEL_BASIC, 1, 1, STP_CHANNEL_NONE, 1, 0
78  },
79  {
80    "ModelName", N_("Model Name"), "Color=Yes,Category=Basic Printer Setup",
81    N_("PPD File Model Name"),
82    STP_PARAMETER_TYPE_STRING_LIST, STP_PARAMETER_CLASS_CORE,
83    STP_PARAMETER_LEVEL_INTERNAL, 0, 0, STP_CHANNEL_NONE, 0, 0
84  },
85  {
86    "PrintingMode", N_("Printing Mode"), "Color=Yes,Category=Core Parameter",
87    N_("Printing Output Mode"),
88    STP_PARAMETER_TYPE_STRING_LIST, STP_PARAMETER_CLASS_CORE,
89    STP_PARAMETER_LEVEL_BASIC, 1, 1, STP_CHANNEL_NONE, 1, 0
90  },
91};
92
93static const int the_parameter_count =
94sizeof(the_parameters) / sizeof(const stp_parameter_t);
95
96static int
97ps_option_to_param(stp_parameter_t *param, stp_mxml_node_t *option)
98{
99  const char *group_text = stp_mxmlElementGetAttr(option, "grouptext");
100
101  if (group_text != NULL)
102    param->category = group_text;
103  else
104    param->category = NULL;
105
106  param->text = stp_mxmlElementGetAttr(option, "text");
107  param->help = stp_mxmlElementGetAttr(option, "text");
108  if (stp_mxmlElementGetAttr(option, "stptype"))
109    {
110      const char *default_value = stp_mxmlElementGetAttr(option, "default");
111      double stp_default_value = strtod(stp_mxmlElementGetAttr(option, "stpdefault"), 0);
112      double lower_bound = strtod(stp_mxmlElementGetAttr(option, "stplower"), NULL);
113      double upper_bound = strtod(stp_mxmlElementGetAttr(option, "stpupper"), NULL);
114      param->p_type = atoi(stp_mxmlElementGetAttr(option, "stptype"));
115      param->is_mandatory = atoi(stp_mxmlElementGetAttr(option, "stpmandatory"));
116      param->p_class = atoi(stp_mxmlElementGetAttr(option, "stpclass"));
117      param->p_level = atoi(stp_mxmlElementGetAttr(option, "stplevel"));
118      param->channel = (unsigned char) atoi(stp_mxmlElementGetAttr(option, "stpchannel"));
119      param->read_only = 0;
120      param->is_active = 1;
121      param->verify_this_parameter = 1;
122      param->name = stp_mxmlElementGetAttr(option, "stpname");
123      stp_deprintf(STP_DBG_PS,
124		   "Gutenprint parameter %s type %d mandatory %d class %d level %d channel %d default %s %f",
125		   param->name, param->p_type, param->is_mandatory,
126		   param->p_class, param->p_level, param->channel,
127		   default_value, stp_default_value);
128      switch (param->p_type)
129	{
130	case STP_PARAMETER_TYPE_DOUBLE:
131	  param->deflt.dbl = stp_default_value;
132	  param->bounds.dbl.upper = upper_bound;
133	  param->bounds.dbl.lower = lower_bound;
134	  stp_deprintf(STP_DBG_PS, " %.3f %.3f %.3f\n",
135		       param->deflt.dbl, param->bounds.dbl.upper,
136		       param->bounds.dbl.lower);
137	  break;
138	case STP_PARAMETER_TYPE_DIMENSION:
139	  param->deflt.dimension = atoi(default_value);
140	  param->bounds.dimension.upper = (int) upper_bound;
141	  param->bounds.dimension.lower = (int) lower_bound;
142	  stp_deprintf(STP_DBG_PS, " %d %d %d\n",
143		       param->deflt.dimension, param->bounds.dimension.upper,
144		       param->bounds.dimension.lower);
145	  break;
146	case STP_PARAMETER_TYPE_INT:
147	  param->deflt.integer = atoi(default_value);
148	  param->bounds.integer.upper = (int) upper_bound;
149	  param->bounds.integer.lower = (int) lower_bound;
150	  stp_deprintf(STP_DBG_PS, " %d %d %d\n",
151		       param->deflt.integer, param->bounds.integer.upper,
152		       param->bounds.integer.lower);
153	  break;
154	case STP_PARAMETER_TYPE_BOOLEAN:
155	  param->deflt.boolean = strcasecmp(default_value, "true") == 0 ? 1 : 0;
156	  stp_deprintf(STP_DBG_PS, " %d\n", param->deflt.boolean);
157	  break;
158	default:
159	  stp_deprintf(STP_DBG_PS, "\n");
160	  break;
161	}
162    }
163  else
164    {
165      const char *ui = stp_mxmlElementGetAttr(option, "ui");
166      param->name = stp_mxmlElementGetAttr(option, "name");
167      if (strcasecmp(ui, "Boolean") == 0)
168	param->p_type = STP_PARAMETER_TYPE_BOOLEAN;
169      else
170	param->p_type = STP_PARAMETER_TYPE_STRING_LIST;
171      if (strcmp(param->name, "PageSize") == 0)
172	param->p_class = STP_PARAMETER_CLASS_CORE;
173      else
174	param->p_class = STP_PARAMETER_CLASS_FEATURE;
175      param->p_level = STP_PARAMETER_LEVEL_BASIC;
176      param->is_mandatory = 1;
177      param->is_active = 1;
178      param->channel = -1;
179      param->verify_this_parameter = 1;
180      param->read_only = 0;
181    }
182
183  return 0;
184}
185
186/*
187 * 'ps_parameters()' - Return the parameter values for the given parameter.
188 */
189
190static int
191ppd_whitespace_callback(stp_mxml_node_t *node, int where)
192{
193  return 0;
194}
195
196static int
197check_ppd_file(const stp_vars_t *v)
198{
199  const char *ppd_file = stp_get_file_parameter(v, "PPDFile");
200
201  if (ppd_file == NULL || ppd_file[0] == 0)
202    {
203      stp_dprintf(STP_DBG_PS, v, "Empty PPD file\n");
204      return 0;
205    }
206  else if (m_ppd_file && strcmp(m_ppd_file, ppd_file) == 0)
207    {
208      stp_dprintf(STP_DBG_PS, v, "Not replacing PPD file %s\n", m_ppd_file);
209      return 1;
210    }
211  else
212    {
213      stp_dprintf(STP_DBG_PS, v, "Replacing PPD file %s with %s\n",
214		  m_ppd_file ? m_ppd_file : "(null)",
215		  ppd_file ? ppd_file : "(null)");
216      if (m_ppd != NULL)
217	stp_mxmlDelete(m_ppd);
218      m_ppd = NULL;
219
220      if (m_ppd_file)
221	stp_free(m_ppd_file);
222      m_ppd_file = NULL;
223
224      if ((m_ppd = stpi_xmlppd_read_ppd_file(ppd_file)) == NULL)
225	{
226	  stp_eprintf(v, "Unable to open PPD file %s\n", ppd_file);
227	  return 0;
228	}
229      if (stp_get_debug_level() & STP_DBG_PS)
230	{
231	  char *ppd_stuff = stp_mxmlSaveAllocString(m_ppd, ppd_whitespace_callback);
232	  stp_dprintf(STP_DBG_PS, v, "%s", ppd_stuff);
233	  stp_free(ppd_stuff);
234	}
235
236      m_ppd_file = stp_strdup(ppd_file);
237      return 1;
238    }
239}
240
241
242static stp_parameter_list_t
243ps_list_parameters(const stp_vars_t *v)
244{
245  stp_parameter_list_t *ret = stp_parameter_list_create();
246  stp_mxml_node_t *option;
247  int i;
248  int status = check_ppd_file(v);
249  stp_dprintf(STP_DBG_PS, v, "Adding parameters from %s (%d)\n",
250	      m_ppd_file ? m_ppd_file : "(null)", status);
251
252  for (i = 0; i < the_parameter_count; i++)
253    stp_parameter_list_add_param(ret, &(the_parameters[i]));
254
255  if (status)
256    {
257      int num_options = stpi_xmlppd_find_option_count(m_ppd);
258      stp_dprintf(STP_DBG_PS, v, "Found %d parameters\n", num_options);
259      for (i=0; i < num_options; i++)
260	{
261	  /* MEMORY LEAK!!! */
262	  stp_parameter_t *param = stp_malloc(sizeof(stp_parameter_t));
263	  option = stpi_xmlppd_find_option_index(m_ppd, i);
264	  if (option)
265	    {
266	      ps_option_to_param(param, option);
267	      if (param->p_type != STP_PARAMETER_TYPE_INVALID &&
268		  strcmp(param->name, "PageRegion") != 0 &&
269		  strcmp(param->name, "PageSize") != 0)
270		{
271		  stp_dprintf(STP_DBG_PS, v, "Adding parameter %s %s\n",
272			      param->name, param->text);
273		  stp_parameter_list_add_param(ret, param);
274		}
275	      else
276		stp_free(param);
277	    }
278	}
279    }
280  return ret;
281}
282
283static void
284ps_parameters_internal(const stp_vars_t *v, const char *name,
285		       stp_parameter_t *description)
286{
287  int		i;
288  stp_mxml_node_t *option;
289  int status = 0;
290  int num_choices;
291  const char *defchoice;
292
293  description->p_type = STP_PARAMETER_TYPE_INVALID;
294  description->deflt.str = 0;
295  description->is_active = 0;
296
297  if (name == NULL)
298    return;
299
300  status = check_ppd_file(v);
301
302  for (i = 0; i < the_parameter_count; i++)
303  {
304    if (strcmp(name, the_parameters[i].name) == 0)
305      {
306	stp_fill_parameter_settings(description, &(the_parameters[i]));
307	if (strcmp(name, "PPDFile") == 0)
308	  description->is_active = 1;
309	else if (strcmp(name, "ModelName") == 0)
310	  {
311	    const char *nickname;
312	    description->bounds.str = stp_string_list_create();
313	    if (m_ppd && stp_mxmlElementGetAttr(m_ppd, "nickname"))
314	      nickname = stp_mxmlElementGetAttr(m_ppd, "nickname");
315	    else
316	      nickname = _("None; please provide a PPD file");
317	    stp_string_list_add_string(description->bounds.str,
318				       nickname, nickname);
319	    description->deflt.str = nickname;
320	    description->is_active = 1;
321	    return;
322	  }
323	else if (strcmp(name, "PrintingMode") == 0)
324	  {
325	    if (! m_ppd || strcmp(stp_mxmlElementGetAttr(m_ppd, "color"), "1") == 0)
326	      {
327		description->bounds.str = stp_string_list_create();
328		stp_string_list_add_string
329		  (description->bounds.str, "Color", _("Color"));
330		stp_string_list_add_string
331		  (description->bounds.str, "BW", _("Black and White"));
332		description->deflt.str =
333		  stp_string_list_param(description->bounds.str, 0)->name;
334		description->is_active = 1;
335	      }
336	    else
337	      description->is_active = 0;
338	    return;
339	  }
340      }
341  }
342
343  if (!status && strcmp(name, "PageSize") != 0)
344    return;
345  if ((option = stpi_xmlppd_find_option_named(m_ppd, name)) == NULL)
346  {
347    if (strcmp(name, "PageSize") == 0)
348      {
349	/* Provide a default set of page sizes */
350	description->bounds.str = stp_string_list_create();
351	stp_string_list_add_string
352	  (description->bounds.str, "Letter", _("Letter"));
353	stp_string_list_add_string
354	  (description->bounds.str, "A4", _("A4"));
355	stp_string_list_add_string
356	  (description->bounds.str, "Custom", _("Custom"));
357	description->deflt.str =
358	  stp_string_list_param(description->bounds.str, 0)->name;
359	description->is_active = 1;
360	return;
361      }
362    else
363      {
364	char *tmp = stp_malloc(strlen(name) + 4);
365	strcpy(tmp, "Stp");
366	strncat(tmp, name, strlen(name) + 3);
367	if ((option = stpi_xmlppd_find_option_named(m_ppd, tmp)) == NULL)
368	  {
369	    stp_dprintf(STP_DBG_PS, v, "no parameter %s", name);
370	    stp_free(tmp);
371	    return;
372	  }
373	stp_free(tmp);
374      }
375  }
376
377  ps_option_to_param(description, option);
378  if (description->p_type != STP_PARAMETER_TYPE_STRING_LIST)
379    return;
380  num_choices = atoi(stp_mxmlElementGetAttr(option, "num_choices"));
381  defchoice = stp_mxmlElementGetAttr(option, "default");
382  description->bounds.str = stp_string_list_create();
383
384  stp_dprintf(STP_DBG_PS, v, "describe parameter %s, output name=[%s] text=[%s] category=[%s] choices=[%d] default=[%s]\n",
385	      name, description->name, description->text,
386	      description->category, num_choices, defchoice);
387
388  /* Describe all choices for specified option. */
389  for (i=0; i < num_choices; i++)
390  {
391    stp_mxml_node_t *choice = stpi_xmlppd_find_choice_index(option, i);
392    const char *choice_name = stp_mxmlElementGetAttr(choice, "name");
393    const char *choice_text = stp_mxmlElementGetAttr(choice, "text");
394    stp_string_list_add_string(description->bounds.str, choice_name, choice_text);
395    stp_dprintf(STP_DBG_PS, v, "    parameter %s, choice %d [%s] [%s]",
396		name, i, choice_name, choice_text);
397    if (strcmp(choice_name, defchoice) == 0)
398      {
399	stp_dprintf(STP_DBG_PS, v,
400		    "        parameter %s, choice %d [%s] DEFAULT\n",
401		    name, i, choice_name);
402	description->deflt.str = choice_name;
403      }
404  }
405
406  if (!description->deflt.str)
407    {
408	stp_dprintf(STP_DBG_PS, v,
409		    "        parameter %s, defaulting to [%s]",
410		    name, stp_string_list_param(description->bounds.str, 0)->name);
411	description->deflt.str = stp_string_list_param(description->bounds.str, 0)->name;
412    }
413  if (stp_string_list_count(description->bounds.str) > 0)
414    description->is_active = 1;
415  return;
416}
417
418static void
419ps_parameters(const stp_vars_t *v, const char *name,
420	      stp_parameter_t *description)
421{
422#ifdef HAVE_LOCALE_H
423  char *locale = stp_strdup(setlocale(LC_ALL, NULL));
424  setlocale(LC_ALL, "C");
425#endif
426  ps_parameters_internal(v, name, description);
427#ifdef HAVE_LOCALE_H
428  setlocale(LC_ALL, locale);
429  stp_free(locale);
430#endif
431}
432
433/*
434 * 'ps_media_size()' - Return the size of the page.
435 */
436
437static void
438ps_media_size_internal(const stp_vars_t *v,		/* I */
439		       int  *width,		/* O - Width in points */
440		       int  *height)		/* O - Height in points */
441{
442  const char *pagesize = stp_get_string_parameter(v, "PageSize");
443  int status = check_ppd_file(v);
444  if (!pagesize)
445    pagesize = "";
446
447  stp_dprintf(STP_DBG_PS, v,
448	      "ps_media_size(%d, \'%s\', \'%s\', %p, %p)\n",
449	      stp_get_model_id(v), m_ppd_file, pagesize,
450	      (void *) width, (void *) height);
451
452  stp_default_media_size(v, width, height);
453
454  if (status)
455    {
456      stp_mxml_node_t *paper = stpi_xmlppd_find_page_size(m_ppd, pagesize);
457      if (paper)
458	{
459	  *width = atoi(stp_mxmlElementGetAttr(paper, "width"));
460	  *height = atoi(stp_mxmlElementGetAttr(paper, "height"));
461	}
462      else
463	{
464	  *width = 0;
465	  *height = 0;
466	}
467    }
468
469  stp_dprintf(STP_DBG_PS, v, "dimensions %d %d\n", *width, *height);
470  return;
471}
472
473static void
474ps_media_size(const stp_vars_t *v, int *width, int *height)
475{
476#ifdef HAVE_LOCALE_H
477  char *locale = stp_strdup(setlocale(LC_ALL, NULL));
478  setlocale(LC_ALL, "C");
479#endif
480  ps_media_size_internal(v, width, height);
481#ifdef HAVE_LOCALE_H
482  setlocale(LC_ALL, locale);
483  stp_free(locale);
484#endif
485}
486
487/*
488 * 'ps_imageable_area()' - Return the imageable area of the page.
489 */
490
491static void
492ps_imageable_area_internal(const stp_vars_t *v,      /* I */
493			   int  use_max_area, /* I - Use maximum area */
494			   int  *left,	/* O - Left position in points */
495			   int  *right,	/* O - Right position in points */
496			   int  *bottom, /* O - Bottom position in points */
497			   int  *top)	/* O - Top position in points */
498{
499  int width, height;
500  const char *pagesize = stp_get_string_parameter(v, "PageSize");
501  if (!pagesize)
502    pagesize = "";
503
504  /* Set some defaults. */
505  ps_media_size_internal(v, &width, &height);
506  *left   = 0;
507  *right  = width;
508  *top    = 0;
509  *bottom = height;
510
511  if (check_ppd_file(v))
512    {
513      stp_mxml_node_t *paper = stpi_xmlppd_find_page_size(m_ppd, pagesize);
514      if (paper)
515	{
516	  double pleft = atoi(stp_mxmlElementGetAttr(paper, "left"));
517	  double pright = atoi(stp_mxmlElementGetAttr(paper, "right"));
518	  double ptop = atoi(stp_mxmlElementGetAttr(paper, "top"));
519	  double pbottom = atoi(stp_mxmlElementGetAttr(paper, "bottom"));
520	  stp_dprintf(STP_DBG_PS, v, "size=l %f r %f b %f t %f h %d w %d\n",
521		      pleft, pright, pbottom, ptop, height, width);
522	  *left = (int) pleft;
523	  *right = (int) pright;
524	  *top = height - (int) ptop;
525	  *bottom = height - (int) pbottom;
526	  stp_dprintf(STP_DBG_PS, v, ">>>> l %d r %d b %d t %d h %d w %d\n",
527		      *left, *right, *bottom, *top, height, width);
528	}
529    }
530
531  if (use_max_area)
532  {
533    if (*left > 0)
534      *left = 0;
535    if (*right < width)
536      *right = width;
537    if (*top > 0)
538      *top = 0;
539    if (*bottom < height)
540      *bottom = height;
541  }
542
543  stp_dprintf(STP_DBG_PS, v, "pagesize %s max_area=%d l %d r %d b %d t %d h %d w %d\n",
544	      pagesize ? pagesize : "(null)",
545	      use_max_area, *left, *right, *bottom, *top, width, height);
546
547  return;
548}
549
550static void
551ps_imageable_area(const stp_vars_t *v,      /* I */
552                  int  *left,		/* O - Left position in points */
553                  int  *right,		/* O - Right position in points */
554                  int  *bottom,		/* O - Bottom position in points */
555                  int  *top)		/* O - Top position in points */
556{
557#ifdef HAVE_LOCALE_H
558  char *locale = stp_strdup(setlocale(LC_ALL, NULL));
559  setlocale(LC_ALL, "C");
560#endif
561  ps_imageable_area_internal(v, 0, left, right, bottom, top);
562#ifdef HAVE_LOCALE_H
563  setlocale(LC_ALL, locale);
564  stp_free(locale);
565#endif
566}
567
568static void
569ps_maximum_imageable_area(const stp_vars_t *v,      /* I */
570			  int  *left,	/* O - Left position in points */
571			  int  *right,	/* O - Right position in points */
572			  int  *bottom,	/* O - Bottom position in points */
573			  int  *top)	/* O - Top position in points */
574{
575#ifdef HAVE_LOCALE_H
576  char *locale = stp_strdup(setlocale(LC_ALL, NULL));
577  setlocale(LC_ALL, "C");
578#endif
579  ps_imageable_area_internal(v, 1, left, right, bottom, top);
580#ifdef HAVE_LOCALE_H
581  setlocale(LC_ALL, locale);
582  stp_free(locale);
583#endif
584}
585
586static void
587ps_limit(const stp_vars_t *v,  		/* I */
588	 int *width,
589	 int *height,
590	 int *min_width,
591	 int *min_height)
592{
593  *width =	INT_MAX;
594  *height =	INT_MAX;
595  *min_width =	1;
596  *min_height =	1;
597}
598
599/*
600 * This is really bogus...
601 */
602static void
603ps_describe_resolution_internal(const stp_vars_t *v, int *x, int *y)
604{
605  const char *resolution = stp_get_string_parameter(v, "Resolution");
606  *x = -1;
607  *y = -1;
608  if (resolution)
609    sscanf(resolution, "%dx%d", x, y);
610  return;
611}
612
613static void
614ps_describe_resolution(const stp_vars_t *v, int *x, int *y)
615{
616#ifdef HAVE_LOCALE_H
617  char *locale = stp_strdup(setlocale(LC_ALL, NULL));
618  setlocale(LC_ALL, "C");
619#endif
620  ps_describe_resolution_internal(v, x, y);
621#ifdef HAVE_LOCALE_H
622  setlocale(LC_ALL, locale);
623  stp_free(locale);
624#endif
625}
626
627static const char *
628ps_describe_output(const stp_vars_t *v)
629{
630  const char *print_mode = stp_get_string_parameter(v, "PrintingMode");
631  const char *input_image_type = stp_get_string_parameter(v, "InputImageType");
632  if (print_mode && strcmp(print_mode, "Color") == 0)
633    {
634      if (input_image_type && (strcmp(input_image_type, "CMYK") == 0 ||
635			       strcmp(input_image_type, "KCMY") == 0))
636	return "CMYK";
637      else
638	return "RGB";
639    }
640  else
641    return "Whitescale";
642}
643
644static stp_string_list_t *
645ps_external_options(const stp_vars_t *v)
646{
647  stp_parameter_list_t param_list = ps_list_parameters(v);
648  stp_string_list_t *answer;
649  char *tmp;
650  char *ppd_name = NULL;
651  int i;
652#ifdef HAVE_LOCALE_H
653  char *locale;
654#endif
655  if (! param_list)
656    return NULL;
657  answer = stp_string_list_create();
658#ifdef HAVE_LOCALE_H
659  locale = stp_strdup(setlocale(LC_ALL, NULL));
660  setlocale(LC_ALL, "C");
661#endif
662  for (i = 0; i < stp_parameter_list_count(param_list); i++)
663    {
664      const stp_parameter_t *param = stp_parameter_list_param(param_list, i);
665      stp_parameter_t desc;
666      stp_describe_parameter(v, param->name, &desc);
667      if (desc.is_active)
668	{
669	  stp_mxml_node_t *option;
670	  if (m_ppd &&
671	      (option = stpi_xmlppd_find_option_named(m_ppd, desc.name)) == NULL)
672	    {
673	      ppd_name = stp_malloc(strlen(desc.name) + 4);
674	      strcpy(ppd_name, "Stp");
675	      strncat(ppd_name, desc.name, strlen(desc.name) + 3);
676	      if ((option = stpi_xmlppd_find_option_named(m_ppd, ppd_name)) == NULL)
677		{
678		  stp_dprintf(STP_DBG_PS, v, "no parameter %s", desc.name);
679		  STP_SAFE_FREE(ppd_name);
680		}
681	    }
682	  switch (desc.p_type)
683	    {
684	    case STP_PARAMETER_TYPE_STRING_LIST:
685	      if (stp_get_string_parameter(v, desc.name) &&
686		  strcmp(stp_get_string_parameter(v, desc.name),
687			 desc.deflt.str))
688		{
689		  stp_dprintf(STP_DBG_PS, v, "Adding string parameter %s (%s): %s %s\n",
690			      desc.name, ppd_name ? ppd_name : "(null)",
691			      stp_get_string_parameter(v, desc.name),
692			      desc.deflt.str);
693		  stp_string_list_add_string(answer,
694					     ppd_name ? ppd_name : desc.name,
695					     stp_get_string_parameter(v, desc.name));
696		}
697	      break;
698	    case STP_PARAMETER_TYPE_INT:
699	      if (stp_get_int_parameter(v, desc.name) != desc.deflt.integer)
700		{
701		  stp_dprintf(STP_DBG_PS, v, "Adding integer parameter %s (%s): %d %d\n",
702			      desc.name, ppd_name ? ppd_name : "(null)",
703			      stp_get_int_parameter(v, desc.name),
704			      desc.deflt.integer);
705		  stp_asprintf(&tmp, "%d", stp_get_int_parameter(v, desc.name));
706		  stp_string_list_add_string(answer,
707					     ppd_name ? ppd_name : desc.name,
708					     tmp);
709		  stp_free(tmp);
710		}
711	      break;
712	    case STP_PARAMETER_TYPE_BOOLEAN:
713	      if (stp_get_boolean_parameter(v, desc.name) != desc.deflt.boolean)
714		{
715		  stp_dprintf(STP_DBG_PS, v, "Adding boolean parameter %s (%s): %d %d\n",
716			      desc.name, ppd_name ? ppd_name : "(null)",
717			      stp_get_boolean_parameter(v, desc.name),
718			      desc.deflt.boolean);
719		  stp_asprintf(&tmp, "%s",
720			       stp_get_boolean_parameter(v, desc.name) ?
721			       "True" : "False");
722		  stp_string_list_add_string(answer,
723					     ppd_name ? ppd_name : desc.name,
724					     tmp);
725		  stp_free(tmp);
726		}
727	      break;
728	    case STP_PARAMETER_TYPE_DOUBLE:
729	      if (fabs(stp_get_float_parameter(v, desc.name) - desc.deflt.dbl) > .00001)
730		{
731		  stp_dprintf(STP_DBG_PS, v, "Adding float parameter %s (%s): %.3f %.3f\n",
732			      desc.name, ppd_name ? ppd_name : "(null)",
733			      stp_get_float_parameter(v, desc.name),
734			      desc.deflt.dbl);
735		  stp_asprintf(&tmp, "%.3f",
736			       stp_get_float_parameter(v, desc.name));
737		  stp_string_list_add_string(answer,
738					     ppd_name ? ppd_name : desc.name,
739					     tmp);
740		  stp_free(tmp);
741		}
742	      break;
743	    case STP_PARAMETER_TYPE_DIMENSION:
744	      if (stp_get_dimension_parameter(v, desc.name) !=
745		  desc.deflt.dimension)
746		{
747		  stp_dprintf(STP_DBG_PS, v, "Adding dimension parameter %s (%s): %d %d\n",
748			      desc.name, ppd_name ? ppd_name : "(null)",
749			      stp_get_dimension_parameter(v, desc.name),
750			      desc.deflt.dimension);
751		  stp_asprintf(&tmp, "%d",
752			       stp_get_dimension_parameter(v, desc.name));
753		  stp_string_list_add_string(answer,
754					     ppd_name ? ppd_name : desc.name,
755					     tmp);
756		  stp_free(tmp);
757		}
758	      break;
759	    default:
760	      break;
761	    }
762	  STP_SAFE_FREE(ppd_name);
763	}
764      stp_parameter_description_destroy(&desc);
765    }
766#ifdef HAVE_LOCALE_H
767  setlocale(LC_ALL, locale);
768  stp_free(locale);
769#endif
770  return answer;
771}
772
773/*
774 * 'ps_print_device_settings()' - output postscript code from PPD into the
775 * postscript stream.
776 */
777
778static void
779ps_print_device_settings(stp_vars_t *v)
780{
781  int i;
782  stp_parameter_list_t param_list = ps_list_parameters(v);
783  if (! param_list)
784    return;
785  stp_puts("%%BeginSetup\n", v);
786  for (i = 0; i < stp_parameter_list_count(param_list); i++)
787    {
788      const stp_parameter_t *param = stp_parameter_list_param(param_list, i);
789      stp_parameter_t desc;
790      stp_describe_parameter(v, param->name, &desc);
791      if (desc.is_active)
792	{
793	  switch (desc.p_type)
794	    {
795	    case STP_PARAMETER_TYPE_STRING_LIST:
796	    case STP_PARAMETER_TYPE_BOOLEAN:
797	      {
798		const char *val=NULL;
799		const char *defval=NULL;
800
801		/* If this is a bool parameter, set val to "True" or "False" - otherwise fetch from string parameter. */
802		if(desc.p_type==STP_PARAMETER_TYPE_BOOLEAN)
803		  {
804		    val=stp_get_boolean_parameter(v,desc.name) ? "True" : "False";
805		    defval=desc.deflt.boolean ? "True" : "False";
806		  }
807		else
808		  {
809		    val=stp_get_string_parameter(v,desc.name);
810		    defval=desc.deflt.str;
811		  }
812
813		/* We only include the option's code if it's set to a value other than the default. */
814		if(val && defval && (strcmp(val,defval)!=0))
815		  {
816		    if(m_ppd)
817		      {
818			/* If we have a PPD xml tree we hunt for the appropriate "option" and "choice"... */
819			stp_mxml_node_t *node=m_ppd;
820			node=stp_mxmlFindElement(node,node, "option", "name", desc.name, STP_MXML_DESCEND);
821			if(node)
822			  {
823			    node=stp_mxmlFindElement(node,node, "choice", "name", val, STP_MXML_DESCEND);
824			    if(node)
825			      {
826				if(node->child && node->child->value.opaque && (strlen(node->child->value.opaque)>1))
827				  {
828				    /* If we have opaque data for the child, we use %%BeginFeature and copy the code verbatim. */
829				    stp_puts("[{\n", v);
830				    stp_zprintf(v, "%%%%BeginFeature: *%s %s\n", desc.name, val);
831				    if(node->child->value.opaque)
832				      stp_puts(node->child->value.opaque,v);
833				    stp_puts("\n%%EndFeature\n", v);
834				    stp_puts("} stopped cleartomark\n", v);
835				  }
836				else
837				  {
838				    /* If we don't have code, we use %%IncludeFeature instead. */
839				    stp_puts("[{\n", v);
840				    stp_zprintf(v, "%%%%IncludeFeature: *%s %s\n", desc.name, val);
841				    if(node->child->value.opaque)
842				      stp_puts(node->child->value.opaque,v);
843				    stp_puts("} stopped cleartomark\n", v);
844				  }
845			      }
846			  }
847		      }
848		  }
849	      }
850	      break;
851	    case STP_PARAMETER_TYPE_INT:
852	      if(stp_get_int_parameter(v,desc.name)!=desc.deflt.integer)
853		{
854		  stp_puts("[{\n", v);
855		  stp_zprintf(v, "%%%%IncludeFeature: *%s %d\n", desc.name,
856			      stp_get_int_parameter(v, desc.name));
857		  stp_puts("} stopped cleartomark\n", v);
858		}
859	      break;
860	    case STP_PARAMETER_TYPE_DOUBLE:
861	      if(stp_get_float_parameter(v,desc.name)!=desc.deflt.dbl)
862		{
863		  stp_puts("[{\n", v);
864		  stp_zprintf(v, "%%%%IncludeFeature: *%s %f\n", desc.name,
865			      stp_get_float_parameter(v, desc.name));
866		  stp_puts("} stopped cleartomark\n", v);
867		}
868	      break;
869	    case STP_PARAMETER_TYPE_DIMENSION:
870	      if(stp_get_dimension_parameter(v,desc.name)!=desc.deflt.dimension)
871		{
872		  stp_puts("[{\n", v);
873		  stp_zprintf(v, "%%%%IncludeFeature: *%s %d\n", desc.name,
874			      stp_get_dimension_parameter(v, desc.name));
875		  stp_puts("} stopped cleartomark\n", v);
876		}
877	      break;
878	    default:
879	      break;
880	    }
881	}
882      stp_parameter_description_destroy(&desc);
883    }
884  stp_puts("%%EndSetup\n", v);
885  stp_parameter_list_destroy(param_list);
886}
887
888/*
889 * 'ps_print()' - Print an image to a PostScript printer.
890 */
891
892static int
893ps_print_internal(stp_vars_t *v, stp_image_t *image)
894{
895  int		status = 1;
896  int		model = stp_get_model_id(v);
897  const char    *print_mode = stp_get_string_parameter(v, "PrintingMode");
898  const char *input_image_type = stp_get_string_parameter(v, "InputImageType");
899  unsigned short *out = NULL;
900  int		top = stp_get_top(v);
901  int		left = stp_get_left(v);
902  int		y;		/* Looping vars */
903  int		page_left,	/* Left margin of page */
904		page_right,	/* Right margin of page */
905		page_top,	/* Top of page */
906		page_bottom,	/* Bottom of page */
907		page_width,	/* Width of page */
908		page_height,	/* Height of page */
909		paper_width,	/* Width of physical page */
910		paper_height,	/* Height of physical page */
911		out_width,	/* Width of image on page */
912		out_height,	/* Height of image on page */
913		out_channels,	/* Output bytes per pixel */
914		out_ps_height,	/* Output height (Level 2 output) */
915		out_offset;	/* Output offset (Level 2 output) */
916  time_t	curtime;	/* Current time of day */
917  unsigned	zero_mask;
918  int           image_height,
919		image_width;
920  int		color_out = 0;
921  int		cmyk_out = 0;
922
923  if (print_mode && strcmp(print_mode, "Color") == 0)
924    color_out = 1;
925  if (color_out &&
926      input_image_type && (strcmp(input_image_type, "CMYK") == 0 ||
927			   strcmp(input_image_type, "KCMY") == 0))
928    cmyk_out = 1;
929
930  stp_image_init(image);
931
932 /*
933  * Compute the output size...
934  */
935
936  out_width = stp_get_width(v);
937  out_height = stp_get_height(v);
938
939  ps_imageable_area_internal(v, 0, &page_left, &page_right, &page_bottom, &page_top);
940  ps_media_size_internal(v, &paper_width, &paper_height);
941  page_width = page_right - page_left;
942  page_height = page_bottom - page_top;
943
944  image_height = stp_image_height(image);
945  image_width = stp_image_width(image);
946
947 /*
948  * Output a standard PostScript header with DSC comments...
949  */
950
951  curtime = time(NULL);
952
953  top = paper_height - top;
954
955  stp_dprintf(STP_DBG_PS, v,
956	      "out_width = %d, out_height = %d\n", out_width, out_height);
957  stp_dprintf(STP_DBG_PS, v,
958	      "page_left = %d, page_right = %d, page_bottom = %d, page_top = %d\n",
959	      page_left, page_right, page_bottom, page_top);
960  stp_dprintf(STP_DBG_PS, v, "left = %d, top = %d\n", left, top);
961  stp_dprintf(STP_DBG_PS, v, "page_width = %d, page_height = %d\n",
962	      page_width, page_height);
963
964  stp_dprintf(STP_DBG_PS, v, "bounding box l %d b %d r %d t %d\n",
965	      page_left, paper_height - page_bottom,
966	      page_right, paper_height - page_top);
967
968  stp_puts("%!PS-Adobe-3.0\n", v);
969#ifdef HAVE_CONFIG_H
970  stp_zprintf(v, "%%%%Creator: %s/Gutenprint %s (%s)\n",
971	      stp_image_get_appname(image), VERSION, RELEASE_DATE);
972#else
973  stp_zprintf(v, "%%%%Creator: %s/Gutenprint\n", stp_image_get_appname(image));
974#endif
975  stp_zprintf(v, "%%%%CreationDate: %s", ctime(&curtime));
976  stp_zprintf(v, "%%%%BoundingBox: %d %d %d %d\n",
977	      page_left, paper_height - page_bottom,
978	      page_right, paper_height - page_top);
979  stp_puts("%%DocumentData: Clean7Bit\n", v);
980  stp_zprintf(v, "%%%%LanguageLevel: %d\n", model + 1);
981  stp_puts("%%Pages: 1\n", v);
982  stp_puts("%%Orientation: Portrait\n", v);
983  stp_puts("%%EndComments\n", v);
984
985  ps_print_device_settings(v);
986
987 /*
988  * Output the page...
989  */
990
991  stp_puts("%%Page: 1 1\n", v);
992  stp_puts("gsave\n", v);
993
994  stp_zprintf(v, "%d %d translate\n", left, top);
995
996  /* Force locale to "C", because decimal numbers in Postscript must
997     always be printed with a decimal point rather than the
998     locale-specific setting. */
999
1000  stp_zprintf(v, "%.3f %.3f scale\n",
1001	      (double)out_width / ((double)image_width),
1002	      (double)out_height / ((double)image_height));
1003
1004  stp_channel_reset(v);
1005  stp_channel_add(v, 0, 0, 1.0);
1006  if (color_out)
1007    {
1008      stp_channel_add(v, 1, 0, 1.0);
1009      stp_channel_add(v, 2, 0, 1.0);
1010      if (cmyk_out)
1011	{
1012	  stp_channel_add(v, 3, 0, 1.0);
1013	  stp_set_string_parameter(v, "STPIOutputType", "CMYK");
1014	}
1015      else
1016	stp_set_string_parameter(v, "STPIOutputType", "RGB");
1017    }
1018  else
1019    stp_set_string_parameter(v, "STPIOutputType", "Whitescale");
1020
1021  stp_set_boolean_parameter(v, "SimpleGamma", 1);
1022
1023  out_channels = stp_color_init(v, image, 256);
1024
1025  if (model == 0)
1026  {
1027    stp_zprintf(v, "/picture %d string def\n", image_width * out_channels);
1028
1029    stp_zprintf(v, "%d %d 8\n", image_width, image_height);
1030
1031    stp_puts("[ 1 0 0 -1 0 1 ]\n", v);
1032
1033    if (cmyk_out)
1034      stp_puts("{currentfile picture readhexstring pop} false 4 colorimage\n", v);
1035    else if (color_out)
1036      stp_puts("{currentfile picture readhexstring pop} false 3 colorimage\n", v);
1037    else
1038      stp_puts("{currentfile picture readhexstring pop} image\n", v);
1039
1040    for (y = 0; y < image_height; y ++)
1041    {
1042      if (stp_color_get_row(v, image, y, &zero_mask))
1043	{
1044	  status = 2;
1045	  break;
1046	}
1047
1048      out = stp_channel_get_input(v);
1049
1050      /* Convert from KCMY to CMYK */
1051      if (cmyk_out)
1052	{
1053	  int x;
1054	  unsigned short *pos = out;
1055	  for (x = 0; x < image_width; x++, pos += 4)
1056	    {
1057	      unsigned short p0 = pos[0];
1058	      pos[0] = pos[1];
1059	      pos[1] = pos[2];
1060	      pos[2] = pos[3];
1061	      pos[3] = p0;
1062	    }
1063	}
1064      ps_hex(v, out, image_width * out_channels);
1065    }
1066  }
1067  else
1068  {
1069    unsigned short *tmp_buf =
1070      stp_malloc(sizeof(unsigned short) * (image_width * out_channels + 4));
1071    if (cmyk_out)
1072      stp_puts("/DeviceCMYK setcolorspace\n", v);
1073    else if (color_out)
1074      stp_puts("/DeviceRGB setcolorspace\n", v);
1075    else
1076      stp_puts("/DeviceGray setcolorspace\n", v);
1077
1078    stp_puts("<<\n", v);
1079    stp_puts("\t/ImageType 1\n", v);
1080
1081    stp_zprintf(v, "\t/Width %d\n", image_width);
1082    stp_zprintf(v, "\t/Height %d\n", image_height);
1083    stp_puts("\t/BitsPerComponent 8\n", v);
1084
1085    if (cmyk_out)
1086      stp_puts("\t/Decode [ 0 1 0 1 0 1 0 1 ]\n", v);
1087    else if (color_out)
1088      stp_puts("\t/Decode [ 0 1 0 1 0 1 ]\n", v);
1089    else
1090      stp_puts("\t/Decode [ 0 1 ]\n", v);
1091
1092    stp_puts("\t/DataSource currentfile /ASCII85Decode filter\n", v);
1093
1094    if ((image_width * 72 / out_width) < 100)
1095      stp_puts("\t/Interpolate true\n", v);
1096
1097    stp_puts("\t/ImageMatrix [ 1 0 0 -1 0 1 ]\n", v);
1098
1099    stp_puts(">>\n", v);
1100    stp_puts("image\n", v);
1101
1102    for (y = 0, out_offset = 0; y < image_height; y ++)
1103    {
1104      unsigned short *where;
1105      /* FIXME!!! */
1106      if (stp_color_get_row(v, image, y /*, out + out_offset */ , &zero_mask))
1107	{
1108	  status = 2;
1109	  break;
1110	}
1111      out = stp_channel_get_input(v);
1112      if (out_offset > 0)
1113	{
1114	  memcpy(tmp_buf + out_offset, out,
1115		 image_width * out_channels * sizeof(unsigned short));
1116	  where = tmp_buf;
1117	}
1118      else
1119	where = out;
1120
1121      /* Convert from KCMY to CMYK */
1122      if (cmyk_out)
1123	{
1124	  int x;
1125	  unsigned short *pos = where;
1126	  for (x = 0; x < image_width; x++, pos += 4)
1127	    {
1128	      unsigned short p0 = pos[0];
1129	      pos[0] = pos[1];
1130	      pos[1] = pos[2];
1131	      pos[2] = pos[3];
1132	      pos[3] = p0;
1133	    }
1134	}
1135
1136      out_ps_height = out_offset + image_width * out_channels;
1137
1138      if (y < (image_height - 1))
1139      {
1140	ps_ascii85(v, where, out_ps_height & ~3, 0);
1141        out_offset = out_ps_height & 3;
1142      }
1143      else
1144      {
1145        ps_ascii85(v, where, out_ps_height, 1);
1146        out_offset = 0;
1147      }
1148
1149      if (out_offset > 0)
1150        memcpy(tmp_buf, where + out_ps_height - out_offset,
1151	       out_offset * sizeof(unsigned short));
1152    }
1153    stp_free(tmp_buf);
1154  }
1155  stp_image_conclude(image);
1156
1157  stp_puts("grestore\n", v);
1158  stp_puts("showpage\n", v);
1159  stp_puts("%%Trailer\n", v);
1160  stp_puts("%%EOF\n", v);
1161  return status;
1162}
1163
1164static int
1165ps_print(const stp_vars_t *v, stp_image_t *image)
1166{
1167  int status;
1168#ifdef HAVE_LOCALE_H
1169  char *locale;
1170#endif
1171  stp_vars_t *nv = stp_vars_create_copy(v);
1172  stp_prune_inactive_options(nv);
1173  if (!stp_verify(nv))
1174    {
1175      stp_eprintf(nv, "Print options not verified; cannot print.\n");
1176      return 0;
1177    }
1178#ifdef HAVE_LOCALE_H
1179  locale = stp_strdup(setlocale(LC_ALL, NULL));
1180  setlocale(LC_ALL, "C");
1181#endif
1182  status = ps_print_internal(nv, image);
1183#ifdef HAVE_LOCALE_H
1184  setlocale(LC_ALL, locale);
1185  stp_free(locale);
1186#endif
1187  stp_vars_destroy(nv);
1188  return status;
1189}
1190
1191
1192/*
1193 * 'ps_hex()' - Print binary data as a series of hexadecimal numbers.
1194 */
1195
1196static void
1197ps_hex(const stp_vars_t *v,	/* I - File to print to */
1198       unsigned short   *data,	/* I - Data to print */
1199       int              length)	/* I - Number of bytes to print */
1200{
1201  int		col;		/* Current column */
1202  static const char	*hex = "0123456789ABCDEF";
1203
1204  col = 0;
1205  while (length > 0)
1206  {
1207    unsigned char pixel = (*data & 0xff00) >> 8;
1208   /*
1209    * Put the hex chars out to the file; note that we don't use stp_zprintf()
1210    * for speed reasons...
1211    */
1212
1213    stp_putc(hex[pixel >> 4], v);
1214    stp_putc(hex[pixel & 15], v);
1215
1216    data ++;
1217    length --;
1218
1219    col += 2;
1220    if (col >= 72)
1221    {
1222      col = 0;
1223      stp_putc('\n', v);
1224    }
1225  }
1226
1227  if (col > 0)
1228    stp_putc('\n', v);
1229}
1230
1231
1232/*
1233 * 'ps_ascii85()' - Print binary data as a series of base-85 numbers.
1234 */
1235
1236static void
1237ps_ascii85(const stp_vars_t *v,	/* I - File to print to */
1238	   unsigned short *data,	/* I - Data to print */
1239	   int            length,	/* I - Number of bytes to print */
1240	   int            last_line)	/* I - Last line of raster data? */
1241{
1242  int		i;			/* Looping var */
1243  unsigned	b;			/* Binary data word */
1244  unsigned char	c[5];			/* ASCII85 encoded chars */
1245  static int	column = 0;		/* Current column */
1246
1247#define OUTBUF_SIZE 4096
1248  unsigned char outbuffer[OUTBUF_SIZE+10];
1249  int outp=0;
1250
1251  while (length > 3)
1252  {
1253    unsigned char d0 = (data[0] & 0xff00) >> 8;
1254    unsigned char d1 = (data[1] & 0xff00) >> 8;
1255    unsigned char d2 = (data[2] & 0xff00) >> 8;
1256    unsigned char d3 = (data[3] & 0xff00) >> 8;
1257    b = (((((d0 << 8) | d1) << 8) | d2) << 8) | d3;
1258
1259    if (b == 0)
1260    {
1261      outbuffer[outp++]='z';
1262      column ++;
1263    }
1264    else
1265    {
1266      outbuffer[outp+4] = (b % 85) + '!';
1267      b /= 85;
1268      outbuffer[outp+3] = (b % 85) + '!';
1269      b /= 85;
1270      outbuffer[outp+2] = (b % 85) + '!';
1271      b /= 85;
1272      outbuffer[outp+1] = (b % 85) + '!';
1273      b /= 85;
1274      outbuffer[outp] = b + '!';
1275
1276      outp+=5;
1277      column += 5;
1278    }
1279
1280    if (column > 72)
1281    {
1282      outbuffer[outp++]='\n';
1283      column = 0;
1284    }
1285
1286    if(outp>=OUTBUF_SIZE)
1287	{
1288		stp_zfwrite((const char *)outbuffer, outp, 1, v);
1289		outp=0;
1290	}
1291
1292    data += 4;
1293    length -= 4;
1294  }
1295
1296  if(outp)
1297    stp_zfwrite((const char *)outbuffer, outp, 1, v);
1298
1299  if (last_line)
1300  {
1301    if (length > 0)
1302    {
1303      for (b = 0, i = length; i > 0; b = (b << 8) | data[0], data ++, i --);
1304
1305      c[4] = (b % 85) + '!';
1306      b /= 85;
1307      c[3] = (b % 85) + '!';
1308      b /= 85;
1309      c[2] = (b % 85) + '!';
1310      b /= 85;
1311      c[1] = (b % 85) + '!';
1312      b /= 85;
1313      c[0] = b + '!';
1314
1315      stp_zfwrite((const char *)c, length + 1, 1, v);
1316    }
1317
1318    stp_puts("~>\n", v);
1319    column = 0;
1320  }
1321}
1322
1323
1324static const stp_printfuncs_t print_ps_printfuncs =
1325{
1326  ps_list_parameters,
1327  ps_parameters,
1328  ps_media_size,
1329  ps_imageable_area,
1330  ps_maximum_imageable_area,
1331  ps_limit,
1332  ps_print,
1333  ps_describe_resolution,
1334  ps_describe_output,
1335  stp_verify_printer_params,
1336  NULL,
1337  NULL,
1338  ps_external_options
1339};
1340
1341
1342static stp_family_t print_ps_module_data =
1343  {
1344    &print_ps_printfuncs,
1345    NULL
1346  };
1347
1348
1349static int
1350print_ps_module_init(void)
1351{
1352  return stp_family_register(print_ps_module_data.printer_list);
1353}
1354
1355
1356static int
1357print_ps_module_exit(void)
1358{
1359  return stp_family_unregister(print_ps_module_data.printer_list);
1360}
1361
1362
1363/* Module header */
1364#define stp_module_version print_ps_LTX_stp_module_version
1365#define stp_module_data print_ps_LTX_stp_module_data
1366
1367stp_module_version_t stp_module_version = {0, 0};
1368
1369stp_module_t stp_module_data =
1370  {
1371    "ps",
1372    VERSION,
1373    "Postscript family driver",
1374    STP_MODULE_CLASS_FAMILY,
1375    NULL,
1376    print_ps_module_init,
1377    print_ps_module_exit,
1378    (void *) &print_ps_module_data
1379  };
1380
1381