//
// PPD file compiler definitions for the CUPS PPD Compiler.
//
// Copyright © 2020-2024 by OpenPrinting.
// Copyright © 2007-2019 by Apple Inc.
// Copyright © 2002-2006 by Easy Software Products.
//
// Licensed under Apache License v2.0.  See the file "LICENSE" for more
// information.
//

//
// Include necessary headers...
//

#include "ppdc-private.h"


//
// 'ppdcDriver::ppdcDriver()' - Create a new printer driver.
//

ppdcDriver::ppdcDriver(ppdcDriver *d)	// I - Printer driver template
  : ppdcShared()
{
  ppdcGroup	*g;			// Current group


  PPDC_NEW;

  if (d)
  {
    // Bump the use count of any strings we inherit...
    if (d->manufacturer)
      d->manufacturer->retain();
    if (d->version)
      d->version->retain();
    if (d->default_font)
      d->default_font->retain();
    if (d->default_size)
      d->default_size->retain();
    if (d->custom_size_code)
      d->custom_size_code->retain();

    // Copy all of the data from the driver template...
    copyright           = new ppdcArray(d->copyright);
    manufacturer        = d->manufacturer;
    model_name          = 0;
    file_name           = 0;
    pc_file_name        = 0;
    type                = d->type;
    version             = d->version;
    model_number        = d->model_number;
    manual_copies       = d->manual_copies;
    color_device        = d->color_device;
    throughput          = d->throughput;
    attrs               = new ppdcArray(d->attrs);
    constraints         = new ppdcArray(d->constraints);
    filters             = new ppdcArray(d->filters);
    fonts               = new ppdcArray(d->fonts);
    profiles            = new ppdcArray(d->profiles);
    sizes               = new ppdcArray(d->sizes);
    default_font        = d->default_font;
    default_size        = d->default_size;
    variable_paper_size = d->variable_paper_size;
    custom_size_code    = d->custom_size_code;
    left_margin         = d->left_margin;
    bottom_margin       = d->bottom_margin;
    right_margin        = d->right_margin;
    top_margin          = d->top_margin;
    max_width           = d->max_width;
    max_length          = d->max_length;
    min_width           = d->min_width;
    min_length          = d->min_length;

    // Then copy the groups manually, since we want separate copies
    // of the groups and options...
    groups = new ppdcArray();

    for (g = (ppdcGroup *)d->groups->first(); g; g = (ppdcGroup *)d->groups->next())
      groups->add(new ppdcGroup(g));
  }
  else
  {
    // Zero all of the data in the driver...
    copyright           = new ppdcArray();
    manufacturer        = 0;
    model_name          = 0;
    file_name           = 0;
    pc_file_name        = 0;
    version             = 0;
    type                = PPDC_DRIVER_CUSTOM;
    model_number        = 0;
    manual_copies       = 0;
    color_device        = 0;
    throughput          = 1;
    attrs               = new ppdcArray();
    constraints         = new ppdcArray();
    fonts               = new ppdcArray();
    filters             = new ppdcArray();
    groups              = new ppdcArray();
    profiles            = new ppdcArray();
    sizes               = new ppdcArray();
    default_font        = 0;
    default_size        = 0;
    variable_paper_size = 0;
    custom_size_code    = 0;
    left_margin         = 0;
    bottom_margin       = 0;
    right_margin        = 0;
    top_margin          = 0;
    max_width           = 0;
    max_length          = 0;
    min_width           = 0;
    min_length          = 0;
  }
}


//
// 'ppdcDriver::~ppdcDriver()' - Destroy a printer driver.
//

ppdcDriver::~ppdcDriver()
{
  PPDC_DELETE;

  copyright->release();

  if (manufacturer)
    manufacturer->release();
  if (model_name)
    model_name->release();
  if (file_name)
    file_name->release();
  if (pc_file_name)
    pc_file_name->release();
  if (version)
    version->release();
  if (default_font)
    default_font->release();
  if (default_size)
    default_size->release();
  if (custom_size_code)
    custom_size_code->release();

  attrs->release();
  constraints->release();
  filters->release();
  fonts->release();
  groups->release();
  profiles->release();
  sizes->release();
}


//
// 'ppdcDriver::find_attr()' - Find an attribute.
//

ppdcAttr *				// O - Attribute or NULL
ppdcDriver::find_attr(const char *k,	// I - Keyword string
                      const char *s)	// I - Spec string
{
  ppdcAttr	*a;			// Current attribute


  for (a = (ppdcAttr *)attrs->first(); a; a = (ppdcAttr *)attrs->next())
    if (!strcmp(a->name->value, k) &&
        ((!s && (!a->selector->value || !a->selector->value[0])) ||
	 (s && a->selector->value && !strcmp(a->selector->value, s))))
      return (a);

  return (NULL);
}


//
// 'ppdcDriver::find_group()' - Find a group.
//

ppdcGroup *				// O - Matching group or NULL
ppdcDriver::find_group(const char *n)	// I - Group name
{
  ppdcGroup	*g;			// Current group


  for (g = (ppdcGroup *)groups->first(); g; g = (ppdcGroup *)groups->next())
    if (!_cups_strcasecmp(n, g->name->value))
      return (g);

  return (0);
}


//
// 'ppdcDriver::find_option()' - Find an option.
//

ppdcOption *				// O - Matching option or NULL
ppdcDriver::find_option(const char *n)	// I - Option name
{
  return (find_option_group(n, (ppdcGroup **)0));
}


//
// 'ppdcDriver::find_option_group()' - Find an option and its group.
//

ppdcOption *				// O - Matching option or NULL
ppdcDriver::find_option_group(
    const char *n,			// I - Option name
    ppdcGroup  **mg)			// O - Matching group or NULL
{
  ppdcGroup	*g;			// Current group
  ppdcOption	*o;			// Current option


  for (g = (ppdcGroup *)groups->first(); g; g = (ppdcGroup *)groups->next())
    for (o = (ppdcOption *)g->options->first(); o; o = (ppdcOption *)g->options->next())
      if (!_cups_strcasecmp(n, o->name->value))
      {
        if (mg)
	  *mg = g;

        return (o);
      }

  if (mg)
    *mg = (ppdcGroup *)0;

  return (0);
}


//
// 'ppdcDriver::set_custom_size_code()' - Set the custom page size code.
//

void
ppdcDriver::set_custom_size_code(
    const char *c)			// I - CustomPageSize code
{
  if (custom_size_code)
    custom_size_code->release();

  custom_size_code = new ppdcString(c);
}


//
// 'ppdcDriver::set_default_font()' - Set the default font name.
//

void
ppdcDriver::set_default_font(
    ppdcFont *f)			// I - Font
{
  if (default_font)
    default_font->release();

  if (f)
  {
    f->name->retain();
    default_font = f->name;
  }
  else
    default_font = 0;
}


//
// 'ppdcDriver::set_default_size()' - Set the default size name.
//

void
ppdcDriver::set_default_size(
    ppdcMediaSize *m)			// I - Media size
{
  if (default_size)
    default_size->release();

  if (m)
  {
    m->name->retain();
    default_size = m->name;
  }
  else
    default_size = 0;
}


//
// 'ppdcDriver::set_file_name()' - Set the full filename.
//

void
ppdcDriver::set_file_name(const char *f)// I - Filename
{
  if (file_name)
    file_name->release();

  file_name = new ppdcString(f);
}


//
// 'ppdcDriver::set_manufacturer()' - Set the manufacturer name.
//

void
ppdcDriver::set_manufacturer(
    const char *m)			// I - Model name
{
  if (manufacturer)
    manufacturer->release();

  manufacturer = new ppdcString(m);
}


//
// 'ppdcDriver::set_model_name()' - Set the model name.
//

void
ppdcDriver::set_model_name(
    const char *m)			// I - Model name
{
  if (model_name)
    model_name->release();

  model_name = new ppdcString(m);
}


//
// 'ppdcDriver::set_pc_file_name()' - Set the PC filename.
//

void
ppdcDriver::set_pc_file_name(
    const char *f)			// I - Filename
{
  if (pc_file_name)
    pc_file_name->release();

  pc_file_name = new ppdcString(f);
}


//
// 'ppdcDriver::set_version()' - Set the version string.
//

void
ppdcDriver::set_version(const char *v)	// I - Version
{
  if (version)
    version->release();

  version = new ppdcString(v);
}


//
// 'ppdcDriver::write_ppd_file()' - Write a PPD file...
//

int					// O - 0 on success, -1 on failure
ppdcDriver::write_ppd_file(
    cups_file_t    *fp,			// I - PPD file
    ppdcCatalog    *catalog,		// I - Message catalog
    ppdcArray      *locales,		// I - Additional languages to add
    ppdcSource     *src,		// I - Driver source
    ppdcLineEnding le)			// I - Line endings to use
{
  bool			delete_cat;	// Delete the catalog when we are done?
  char			query[42],	// Query attribute
			custom[42];	// Custom attribute
  ppdcString		*s;		// Copyright string
  ppdcGroup		*g;		// Current group
  ppdcOption		*o;		// Current option
  ppdcChoice		*c;		// Current choice
  ppdcMediaSize		*m;		// Current media size
  ppdcProfile		*p;		// Current color profile
  ppdcFilter		*f;		// Current filter
  ppdcFont		*fn,		// Current font
			*bfn;		// Current base font
  ppdcConstraint	*cn;		// Current constraint
  ppdcAttr		*a;		// Current attribute
  const char		*lf;		// Linefeed character to use


  // If we don't have a message catalog, use an empty (English) one...
  if (!catalog)
  {
    catalog    = new ppdcCatalog(NULL);
    delete_cat = true;
  }
  else
    delete_cat = false;

  // Figure out the end-of-line string...
  if (le == PPDC_LFONLY)
    lf = "\n";
  else if (le == PPDC_CRONLY)
    lf = "\r";
  else
    lf = "\r\n";

  // Write the standard header stuff...
  cupsFilePrintf(fp, "*PPD-Adobe: \"4.3\"%s", lf);
  cupsFilePrintf(fp, "*%%%%%%%% PPD file for %s with CUPS.%s",
                 model_name->value, lf);
  cupsFilePrintf(fp,
                 "*%%%%%%%% Created by the CUPS PPD Compiler " CUPS_SVERSION
		 ".%s", lf);
  for (s = (ppdcString *)copyright->first();
       s;
       s = (ppdcString *)copyright->next())
    cupsFilePrintf(fp, "*%% %s%s", catalog->find_message(s->value), lf);
  cupsFilePrintf(fp, "*FormatVersion: \"4.3\"%s", lf);
  cupsFilePrintf(fp, "*FileVersion: \"%s\"%s", version->value, lf);

  a = find_attr("LanguageVersion", NULL);
  cupsFilePrintf(fp, "*LanguageVersion: %s%s",
        	 catalog->find_message(a ? a->value->value : "English"), lf);

  a = find_attr("LanguageEncoding", NULL);
  cupsFilePrintf(fp, "*LanguageEncoding: %s%s",
        	 catalog->find_message(a ? a->value->value : "ISOLatin1"), lf);

  cupsFilePrintf(fp, "*PCFileName: \"%s\"%s", pc_file_name->value, lf);

  for (a = (ppdcAttr *)attrs->first(); a; a = (ppdcAttr *)attrs->next())
    if (!strcmp(a->name->value, "Product"))
      break;

  if (a)
  {
    for (; a; a = (ppdcAttr *)attrs->next())
      if (!strcmp(a->name->value, "Product"))
	cupsFilePrintf(fp, "*Product: \"%s\"%s", a->value->value, lf);
  }
  else
    cupsFilePrintf(fp, "*Product: \"(%s)\"%s", model_name->value, lf);

  cupsFilePrintf(fp, "*Manufacturer: \"%s\"%s",
        	 catalog->find_message(manufacturer->value), lf);

  if ((a = find_attr("ModelName", NULL)) != NULL)
    cupsFilePrintf(fp, "*ModelName: \"%s\"%s",
        	   catalog->find_message(a->value->value), lf);
  else if (_cups_strncasecmp(model_name->value, manufacturer->value,
                       strlen(manufacturer->value)))
    cupsFilePrintf(fp, "*ModelName: \"%s %s\"%s",
        	   catalog->find_message(manufacturer->value),
        	   catalog->find_message(model_name->value), lf);
  else
    cupsFilePrintf(fp, "*ModelName: \"%s\"%s",
        	   catalog->find_message(model_name->value), lf);

  if ((a = find_attr("ShortNickName", NULL)) != NULL)
    cupsFilePrintf(fp, "*ShortNickName: \"%s\"%s",
        	   catalog->find_message(a->value->value), lf);
  else if (_cups_strncasecmp(model_name->value, manufacturer->value,
                       strlen(manufacturer->value)))
    cupsFilePrintf(fp, "*ShortNickName: \"%s %s\"%s",
        	   catalog->find_message(manufacturer->value),
        	   catalog->find_message(model_name->value), lf);
  else
    cupsFilePrintf(fp, "*ShortNickName: \"%s\"%s",
        	   catalog->find_message(model_name->value), lf);

  if ((a = find_attr("NickName", NULL)) != NULL)
    cupsFilePrintf(fp, "*NickName: \"%s\"%s",
        	   catalog->find_message(a->value->value), lf);
  else if (_cups_strncasecmp(model_name->value, manufacturer->value,
                       strlen(manufacturer->value)))
    cupsFilePrintf(fp, "*NickName: \"%s %s, %s\"%s",
        	   catalog->find_message(manufacturer->value),
        	   catalog->find_message(model_name->value), version->value,
		   lf);
  else
    cupsFilePrintf(fp, "*NickName: \"%s, %s\"%s",
        	   catalog->find_message(model_name->value), version->value,
		   lf);

  for (a = (ppdcAttr *)attrs->first(); a; a = (ppdcAttr *)attrs->next())
    if (!strcmp(a->name->value, "PSVersion"))
      break;

  if (a)
  {
    for (; a; a = (ppdcAttr *)attrs->next())
      if (!strcmp(a->name->value, "PSVersion"))
	cupsFilePrintf(fp, "*PSVersion: \"%s\"%s", a->value->value, lf);
  }
  else
    cupsFilePrintf(fp, "*PSVersion: \"(3010.000) 0\"%s", lf);

  if ((a = find_attr("LanguageLevel", NULL)) != NULL)
    cupsFilePrintf(fp, "*LanguageLevel: \"%s\"%s", a->value->value, lf);
  else
    cupsFilePrintf(fp, "*LanguageLevel: \"3\"%s", lf);

  cupsFilePrintf(fp, "*ColorDevice: %s%s", color_device ? "True" : "False", lf);

  if ((a = find_attr("DefaultColorSpace", NULL)) != NULL)
    cupsFilePrintf(fp, "*DefaultColorSpace: %s%s", a->value->value, lf);
  else
    cupsFilePrintf(fp, "*DefaultColorSpace: %s%s",
                   color_device ? "RGB" : "Gray", lf);

  if ((a = find_attr("FileSystem", NULL)) != NULL)
    cupsFilePrintf(fp, "*FileSystem: %s%s", a->value->value, lf);
  else
    cupsFilePrintf(fp, "*FileSystem: False%s", lf);

  cupsFilePrintf(fp, "*Throughput: \"%d\"%s", throughput, lf);

  if ((a = find_attr("LandscapeOrientation", NULL)) != NULL)
    cupsFilePrintf(fp, "*LandscapeOrientation: %s%s", a->value->value, lf);
  else
    cupsFilePrintf(fp, "*LandscapeOrientation: Plus90%s", lf);

  if ((a = find_attr("TTRasterizer", NULL)) != NULL)
    cupsFilePrintf(fp, "*TTRasterizer: %s%s", a->value->value, lf);
  else if (type != PPDC_DRIVER_PS)
    cupsFilePrintf(fp, "*TTRasterizer: Type42%s", lf);

  struct lconv *loc = localeconv();

  if (attrs->count)
  {
    // Write driver-defined attributes...
    cupsFilePrintf(fp, "*%% Driver-defined attributes...%s", lf);
    for (a = (ppdcAttr *)attrs->first(); a; a = (ppdcAttr *)attrs->next())
    {
      if (!strcmp(a->name->value, "Product") ||
          !strcmp(a->name->value, "PSVersion") ||
          !strcmp(a->name->value, "LanguageLevel") ||
          !strcmp(a->name->value, "DefaultColorSpace") ||
          !strcmp(a->name->value, "FileSystem") ||
          !strcmp(a->name->value, "LandscapeOrientation") ||
          !strcmp(a->name->value, "TTRasterizer") ||
          !strcmp(a->name->value, "LanguageVersion") ||
          !strcmp(a->name->value, "LanguageEncoding") ||
          !strcmp(a->name->value, "ModelName") ||
          !strcmp(a->name->value, "NickName") ||
          !strcmp(a->name->value, "ShortNickName") ||
	  !strcmp(a->name->value, "cupsVersion"))
	continue;

      if (a->name->value[0] == '?' &&
          (find_option(a->name->value + 1) ||
	   !strcmp(a->name->value, "?ImageableArea") ||
	   !strcmp(a->name->value, "?PageRegion") ||
	   !strcmp(a->name->value, "?PageSize") ||
	   !strcmp(a->name->value, "?PaperDimension")))
        continue;

      if (!strncmp(a->name->value, "Custom", 6) &&
          find_option(a->name->value + 6))
	continue;

      if (!strncmp(a->name->value, "ParamCustom", 11) &&
          find_option(a->name->value + 11))
	continue;

      if (!a->selector->value || !a->selector->value[0])
	cupsFilePrintf(fp, "*%s", a->name->value);
      else if (!a->text->value || !a->text->value[0])
	cupsFilePrintf(fp, "*%s %s", a->name->value, a->selector->value);
      else
	cupsFilePrintf(fp, "*%s %s/%s", a->name->value, a->selector->value,
        	       a->text->value);

      if (strcmp(a->value->value, "False") &&
          strcmp(a->value->value, "True") &&
	  strcmp(a->name->value, "1284Modes") &&
	  strcmp(a->name->value, "InkName") &&
	  strcmp(a->name->value, "PageStackOrder") &&
	  strncmp(a->name->value, "ParamCustom", 11) &&
	  strcmp(a->name->value, "Protocols") &&
	  strcmp(a->name->value, "ReferencePunch") &&
	  strncmp(a->name->value, "Default", 7))
      {
	cupsFilePrintf(fp, ": \"%s\"%s", a->value->value, lf);

	if (strchr(a->value->value, '\n') || strchr(a->value->value, '\r'))
          cupsFilePrintf(fp, "*End%s", lf);
      }
      else
	cupsFilePrintf(fp, ": %s%s", a->value->value, lf);
    }
  }

  if (type != PPDC_DRIVER_PS || filters->count)
  {
    if ((a = find_attr("cupsVersion", NULL)) != NULL)
      cupsFilePrintf(fp, "*cupsVersion: %s%s", a->value->value, lf);
    else
      cupsFilePrintf(fp, "*cupsVersion: %d.%d%s", CUPS_VERSION_MAJOR,
		     CUPS_VERSION_MINOR, lf);
    cupsFilePrintf(fp, "*cupsModelNumber: %d%s", model_number, lf);
    cupsFilePrintf(fp, "*cupsManualCopies: %s%s",
                   manual_copies ? "True" : "False", lf);

    if (filters->count)
    {
      for (f = (ppdcFilter *)filters->first();
           f;
	   f = (ppdcFilter *)filters->next())
	cupsFilePrintf(fp, "*cupsFilter: \"%s %d %s\"%s", f->mime_type->value,
	               f->cost, f->program->value, lf);
    }
    else
    {
      switch (type)
      {
        case PPDC_DRIVER_LABEL :
	    cupsFilePrintf(fp, "*cupsFilter: \"application/vnd.cups-raster 50 "
	        	     "rastertolabel\"%s", lf);
	    break;

        case PPDC_DRIVER_EPSON :
	    cupsFilePrintf(fp, "*cupsFilter: \"application/vnd.cups-raster 50 "
	        	     "rastertoepson\"%s", lf);
	    break;

        case PPDC_DRIVER_ESCP :
	    cupsFilePrintf(fp, "*cupsFilter: \"application/vnd.cups-command 50 "
	        	     "commandtoescpx\"%s", lf);
	    cupsFilePrintf(fp, "*cupsFilter: \"application/vnd.cups-raster 50 "
	        	     "rastertoescpx\"%s", lf);
	    break;

        case PPDC_DRIVER_HP :
	    cupsFilePrintf(fp, "*cupsFilter: \"application/vnd.cups-raster 50 "
	        	     "rastertohp\"%s", lf);
	    break;

        case PPDC_DRIVER_PCL :
	    cupsFilePrintf(fp, "*cupsFilter: \"application/vnd.cups-command 50 "
	        	     "commandtopclx\"%s", lf);
	    cupsFilePrintf(fp, "*cupsFilter: \"application/vnd.cups-raster 50 "
	        	     "rastertopclx\"%s", lf);
	    break;

	default :
	    break;
      }
    }

    for (p = (ppdcProfile *)profiles->first();
         p;
	 p = (ppdcProfile *)profiles->next())
    {
      char density[255], gamma[255], profile[9][255];

      _cupsStrFormatd(density, density + sizeof(density), p->density, loc);
      _cupsStrFormatd(gamma, gamma + sizeof(gamma), p->gamma, loc);

      for (int i = 0; i < 9; i ++)
	_cupsStrFormatd(profile[i], profile[i] + sizeof(profile[0]),
	                p->profile[i], loc);

      cupsFilePrintf(fp,
                     "*cupsColorProfile %s/%s: \"%s %s %s %s %s %s %s %s %s %s "
		     "%s\"%s", p->resolution->value, p->media_type->value,
		     density, gamma, profile[0], profile[1], profile[2],
		     profile[3], profile[4], profile[5], profile[6], profile[7],
		     profile[8], lf);
    }
  }

  if (locales)
  {
    // Add localizations for additional languages...
    ppdcString	*locale;		// Locale name
    ppdcCatalog	*locatalog;		// Message catalog for locale


    // Write the list of languages...
    cupsFilePrintf(fp, "*cupsLanguages: \"en");

    for (locale = (ppdcString *)locales->first();
         locale;
	 locale = (ppdcString *)locales->next())
    {
      // Skip (US) English...
      if (!strcmp(locale->value, "en") || !strcmp(locale->value, "en_US"))
        continue;

      // See if we have a po file for this language...
      if (!src->find_po(locale->value))
      {
        // No, see if we can use the base file?
        locatalog = new ppdcCatalog(locale->value);

	if (locatalog->messages->count == 0)
	{
	  // No, skip this one...
          _cupsLangPrintf(stderr,
	                  _("ppdc: No message catalog provided for locale "
			    "%s."), locale->value);
          delete locatalog;
          continue;
	}

        // Add the base file to the list...
	src->po_files->add(locatalog);
      }

      cupsFilePrintf(fp, " %s", locale->value);
    }

    cupsFilePrintf(fp, "\"%s", lf);
  }

  for (cn = (ppdcConstraint *)constraints->first();
       cn;
       cn = (ppdcConstraint *)constraints->next())
  {
    // First constrain 1 against 2...
    if (!strncmp(cn->option1->value, "*Custom", 7) ||
        !strncmp(cn->option2->value, "*Custom", 7))
      cupsFilePuts(fp, "*NonUIConstraints: ");
    else
      cupsFilePuts(fp, "*UIConstraints: ");

    if (cn->option1->value[0] != '*')
      cupsFilePutChar(fp, '*');

    cupsFilePuts(fp, cn->option1->value);

    if (cn->choice1->value)
      cupsFilePrintf(fp, " %s", cn->choice1->value);

    cupsFilePutChar(fp, ' ');

    if (cn->option2->value[0] != '*')
      cupsFilePutChar(fp, '*');

    cupsFilePuts(fp, cn->option2->value);

    if (cn->choice2->value)
      cupsFilePrintf(fp, " %s", cn->choice2->value);

    cupsFilePuts(fp, lf);

    // Then constrain 2 against 1...
    if (!strncmp(cn->option1->value, "*Custom", 7) ||
        !strncmp(cn->option2->value, "*Custom", 7))
      cupsFilePuts(fp, "*NonUIConstraints: ");
    else
      cupsFilePuts(fp, "*UIConstraints: ");

    if (cn->option2->value[0] != '*')
      cupsFilePutChar(fp, '*');

    cupsFilePuts(fp, cn->option2->value);

    if (cn->choice2->value)
      cupsFilePrintf(fp, " %s", cn->choice2->value);

    cupsFilePutChar(fp, ' ');

    if (cn->option1->value[0] != '*')
      cupsFilePutChar(fp, '*');

    cupsFilePuts(fp, cn->option1->value);

    if (cn->choice1->value)
      cupsFilePrintf(fp, " %s", cn->choice1->value);

    cupsFilePuts(fp, lf);
  }

  // PageSize option...
  cupsFilePrintf(fp, "*OpenUI *PageSize/Media Size: PickOne%s", lf);
  cupsFilePrintf(fp, "*OrderDependency: 10 AnySetup *PageSize%s", lf);
  cupsFilePrintf(fp, "*DefaultPageSize: %s%s",
                 default_size ? default_size->value : "Letter", lf);

  for (m = (ppdcMediaSize *)sizes->first();
       m;
       m = (ppdcMediaSize *)sizes->next())
    if (m->size_code->value)
    {
      cupsFilePrintf(fp, "*PageSize %s/%s: \"%s\"%s",
        	     m->name->value, catalog->find_message(m->text->value),
		     m->size_code->value, lf);

      if (strchr(m->size_code->value, '\n') ||
          strchr(m->size_code->value, '\r'))
        cupsFilePrintf(fp, "*End%s", lf);
    }
    else
      cupsFilePrintf(fp,
                     "*PageSize %s/%s: \"<</PageSize[%.0f %.0f]"
		     "/ImagingBBox null>>setpagedevice\"%s",
        	     m->name->value, catalog->find_message(m->text->value),
		     m->width, m->length, lf);

  if ((a = find_attr("?PageSize", NULL)) != NULL)
  {
    cupsFilePrintf(fp, "*?PageSize: \"%s\"%s", a->value->value, lf);

    if (strchr(a->value->value, '\n') ||
        strchr(a->value->value, '\r'))
      cupsFilePrintf(fp, "*End%s", lf);
  }

  cupsFilePrintf(fp, "*CloseUI: *PageSize%s", lf);

  // PageRegion option...
  cupsFilePrintf(fp, "*OpenUI *PageRegion/Media Size: PickOne%s", lf);
  cupsFilePrintf(fp, "*OrderDependency: 10 AnySetup *PageRegion%s", lf);
  cupsFilePrintf(fp, "*DefaultPageRegion: %s%s",
                 default_size ? default_size->value : "Letter", lf);

  for (m = (ppdcMediaSize *)sizes->first();
       m;
       m = (ppdcMediaSize *)sizes->next())
    if (m->region_code->value)
    {
      cupsFilePrintf(fp, "*PageRegion %s/%s: \"%s\"%s",
        	     m->name->value, catalog->find_message(m->text->value),
		     m->region_code->value, lf);

      if (strchr(m->region_code->value, '\n') ||
          strchr(m->region_code->value, '\r'))
        cupsFilePrintf(fp, "*End%s", lf);
    }
    else
      cupsFilePrintf(fp,
                     "*PageRegion %s/%s: \"<</PageSize[%.0f %.0f]"
		     "/ImagingBBox null>>setpagedevice\"%s",
        	     m->name->value, catalog->find_message(m->text->value),
		     m->width, m->length, lf);

  if ((a = find_attr("?PageRegion", NULL)) != NULL)
  {
    cupsFilePrintf(fp, "*?PageRegion: \"%s\"%s", a->value->value, lf);

    if (strchr(a->value->value, '\n') ||
        strchr(a->value->value, '\r'))
      cupsFilePrintf(fp, "*End%s", lf);
  }

  cupsFilePrintf(fp, "*CloseUI: *PageRegion%s", lf);

  // ImageableArea info...
  cupsFilePrintf(fp, "*DefaultImageableArea: %s%s",
                 default_size ? default_size->value : "Letter", lf);

  char left[255], right[255], bottom[255], top[255];

  for (m = (ppdcMediaSize *)sizes->first();
       m;
       m = (ppdcMediaSize *)sizes->next())
  {
    _cupsStrFormatd(left, left + sizeof(left), m->left, loc);
    _cupsStrFormatd(bottom, bottom + sizeof(bottom), m->bottom, loc);
    _cupsStrFormatd(right, right + sizeof(right), m->width - m->right, loc);
    _cupsStrFormatd(top, top + sizeof(top), m->length - m->top, loc);

    cupsFilePrintf(fp, "*ImageableArea %s/%s: \"%s %s %s %s\"%s",
                   m->name->value, catalog->find_message(m->text->value),
		   left, bottom, right, top, lf);
  }

  if ((a = find_attr("?ImageableArea", NULL)) != NULL)
  {
    cupsFilePrintf(fp, "*?ImageableArea: \"%s\"%s", a->value->value, lf);

    if (strchr(a->value->value, '\n') ||
        strchr(a->value->value, '\r'))
      cupsFilePrintf(fp, "*End%s", lf);
  }

  // PaperDimension info...
  cupsFilePrintf(fp, "*DefaultPaperDimension: %s%s",
                 default_size ? default_size->value : "Letter", lf);

  char width[255], length[255];

  for (m = (ppdcMediaSize *)sizes->first();
       m;
       m = (ppdcMediaSize *)sizes->next())
  {
    _cupsStrFormatd(width, width + sizeof(width), m->width, loc);
    _cupsStrFormatd(length, length + sizeof(length), m->length, loc);

    cupsFilePrintf(fp, "*PaperDimension %s/%s: \"%s %s\"%s",
                   m->name->value, catalog->find_message(m->text->value),
		   width, length, lf);
  }

  if ((a = find_attr("?PaperDimension", NULL)) != NULL)
  {
    cupsFilePrintf(fp, "*?PaperDimension: \"%s\"%s", a->value->value, lf);

    if (strchr(a->value->value, '\n') ||
        strchr(a->value->value, '\r'))
      cupsFilePrintf(fp, "*End%s", lf);
  }

  // Custom size support...
  if (variable_paper_size)
  {
    _cupsStrFormatd(width, width + sizeof(width), max_width, loc);
    _cupsStrFormatd(length, length + sizeof(length), max_length, loc);

    _cupsStrFormatd(left, left + sizeof(left), left_margin, loc);
    _cupsStrFormatd(bottom, bottom + sizeof(bottom), bottom_margin, loc);
    _cupsStrFormatd(right, right + sizeof(right), right_margin, loc);
    _cupsStrFormatd(top, top + sizeof(top), top_margin, loc);

    cupsFilePrintf(fp, "*MaxMediaWidth: \"%s\"%s", width, lf);
    cupsFilePrintf(fp, "*MaxMediaHeight: \"%s\"%s", length, lf);
    cupsFilePrintf(fp, "*HWMargins: %s %s %s %s%s", left, bottom, right, top,
                   lf);

    if (custom_size_code && custom_size_code->value)
    {
      cupsFilePrintf(fp, "*CustomPageSize True: \"%s\"%s",
                     custom_size_code->value, lf);

      if (strchr(custom_size_code->value, '\n') ||
          strchr(custom_size_code->value, '\r'))
        cupsFilePrintf(fp, "*End%s", lf);
    }
    else
      cupsFilePrintf(fp,
		     "*CustomPageSize True: \"pop pop pop <</PageSize[5 -2 roll]"
		     "/ImagingBBox null>>setpagedevice\"%s", lf);

    if ((a = find_attr("ParamCustomPageSize", "Width")) != NULL)
      cupsFilePrintf(fp, "*ParamCustomPageSize Width: %s%s", a->value->value,
		     lf);
    else
    {
      char width0[255];

      _cupsStrFormatd(width0, width0 + sizeof(width0), min_width, loc);
      _cupsStrFormatd(width, width + sizeof(width), max_width, loc);

      cupsFilePrintf(fp, "*ParamCustomPageSize Width: 1 points %s %s%s",
                     width0, width, lf);
    }

    if ((a = find_attr("ParamCustomPageSize", "Height")) != NULL)
      cupsFilePrintf(fp, "*ParamCustomPageSize Height: %s%s", a->value->value,
		     lf);
    else
    {
      char length0[255];

      _cupsStrFormatd(length0, length0 + sizeof(length0), min_length, loc);
      _cupsStrFormatd(length, length + sizeof(length), max_length, loc);

      cupsFilePrintf(fp, "*ParamCustomPageSize Height: 2 points %s %s%s",
                     length0, length, lf);
    }

    if ((a = find_attr("ParamCustomPageSize", "WidthOffset")) != NULL)
      cupsFilePrintf(fp, "*ParamCustomPageSize WidthOffset: %s%s",
                     a->value->value, lf);
    else
      cupsFilePrintf(fp, "*ParamCustomPageSize WidthOffset: 3 points 0 0%s", lf);

    if ((a = find_attr("ParamCustomPageSize", "HeightOffset")) != NULL)
      cupsFilePrintf(fp, "*ParamCustomPageSize HeightOffset: %s%s",
                     a->value->value, lf);
    else
      cupsFilePrintf(fp, "*ParamCustomPageSize HeightOffset: 4 points 0 0%s", lf);

    if ((a = find_attr("ParamCustomPageSize", "Orientation")) != NULL)
      cupsFilePrintf(fp, "*ParamCustomPageSize Orientation: %s%s",
                     a->value->value, lf);
    else
      cupsFilePrintf(fp, "*ParamCustomPageSize Orientation: 5 int 0 0%s", lf);
  }

  // All other options...
  for (g = (ppdcGroup *)groups->first(); g; g = (ppdcGroup *)groups->next())
  {
    if (!g->options->count)
      continue;

    if (_cups_strcasecmp(g->name->value, "General"))
      cupsFilePrintf(fp, "*OpenGroup: %s/%s%s", g->name->value,
                     catalog->find_message(g->text->value), lf);

    for (o = (ppdcOption *)g->options->first();
         o;
	 o = (ppdcOption *)g->options->next())
    {
      if (!o->choices->count)
        continue;

      if (o->section == PPDC_SECTION_JCL)
      {
	if (!o->text->value)
	  cupsFilePrintf(fp, "*JCLOpenUI *%s/%s: ", o->name->value,
			 catalog->find_message(o->name->value));
	else
	  cupsFilePrintf(fp, "*JCLOpenUI *%s/%s: ", o->name->value,
			 catalog->find_message(o->text->value));
      }
      else if (!o->text->value)
	cupsFilePrintf(fp, "*OpenUI *%s/%s: ", o->name->value,
	               catalog->find_message(o->name->value));
      else
	cupsFilePrintf(fp, "*OpenUI *%s/%s: ", o->name->value,
	               catalog->find_message(o->text->value));

      switch (o->type)
      {
        case PPDC_BOOLEAN :
	    cupsFilePrintf(fp, "Boolean%s", lf);
	    break;
        default :
	    cupsFilePrintf(fp, "PickOne%s", lf);
	    break;
        case PPDC_PICKMANY :
	    cupsFilePrintf(fp, "PickMany%s", lf);
	    break;
      }

      char order[255];
      _cupsStrFormatd(order, order + sizeof(order), o->order, loc);

      cupsFilePrintf(fp, "*OrderDependency: %s ", order);
      switch (o->section)
      {
        default :
	    cupsFilePrintf(fp, "AnySetup");
	    break;
        case PPDC_SECTION_DOCUMENT :
	    cupsFilePrintf(fp, "DocumentSetup");
	    break;
        case PPDC_SECTION_EXIT :
	    cupsFilePrintf(fp, "ExitServer");
	    break;
        case PPDC_SECTION_JCL :
	    cupsFilePrintf(fp, "JCLSetup");
	    break;
        case PPDC_SECTION_PAGE :
	    cupsFilePrintf(fp, "PageSetup");
	    break;
        case PPDC_SECTION_PROLOG :
	    cupsFilePrintf(fp, "Prolog");
	    break;
      }

      cupsFilePrintf(fp, " *%s%s", o->name->value, lf);

      if (o->defchoice)
      {
        // Use the programmer-supplied default...
        cupsFilePrintf(fp, "*Default%s: %s%s", o->name->value,
	               o->defchoice->value, lf);
      }
      else
      {
        // Make the first choice the default...
        c = (ppdcChoice *)o->choices->first();
        cupsFilePrintf(fp, "*Default%s: %s%s", o->name->value, c->name->value,
		       lf);
      }

      for (c = (ppdcChoice *)o->choices->first();
           c;
	   c = (ppdcChoice *)o->choices->next())
      {
        // Write this choice...
	if (!c->text->value)
          cupsFilePrintf(fp, "*%s %s/%s: \"%s\"%s", o->name->value,
                         c->name->value, catalog->find_message(c->name->value),
	        	 c->code->value, lf);
        else
          cupsFilePrintf(fp, "*%s %s/%s: \"%s\"%s", o->name->value,
	                 c->name->value, catalog->find_message(c->text->value),
			 c->code->value, lf);

	// Multi-line commands need a *End line to terminate them.
        if (strchr(c->code->value, '\n') ||
	    strchr(c->code->value, '\r'))
	  cupsFilePrintf(fp, "*End%s", lf);
      }

      snprintf(query, sizeof(query), "?%s", o->name->value);

      if ((a = find_attr(query, NULL)) != NULL)
      {
	cupsFilePrintf(fp, "*%s: \"%s\"%s", query, a->value->value, lf);

	if (strchr(a->value->value, '\n') ||
            strchr(a->value->value, '\r'))
	  cupsFilePrintf(fp, "*End%s", lf);
      }

      if (o->section == PPDC_SECTION_JCL)
	cupsFilePrintf(fp, "*JCLCloseUI: *%s%s", o->name->value, lf);
      else
	cupsFilePrintf(fp, "*CloseUI: *%s%s", o->name->value, lf);

      snprintf(custom, sizeof(custom), "Custom%s", o->name->value);
      if ((a = find_attr(custom, "True")) != NULL)
      {
        // Output custom option information...
        cupsFilePrintf(fp, "*%s True: \"%s\"%s", custom, a->value->value, lf);
	if (strchr(a->value->value, '\n') || strchr(a->value->value, '\r'))
	  cupsFilePrintf(fp, "*End%s", lf);

        snprintf(custom, sizeof(custom), "ParamCustom%s", o->name->value);
	for (a = (ppdcAttr *)attrs->first(); a; a = (ppdcAttr *)attrs->next())
	{
	  if (strcmp(a->name->value, custom))
	    continue;

	  if (!a->selector->value || !a->selector->value[0])
	    cupsFilePrintf(fp, "*%s", a->name->value);
	  else if (!a->text->value || !a->text->value[0])
	    cupsFilePrintf(fp, "*%s %s/%s", a->name->value, a->selector->value,
	                   catalog->find_message(a->selector->value));
	  else
	    cupsFilePrintf(fp, "*%s %s/%s", a->name->value, a->selector->value,
			   catalog->find_message(a->text->value));

          cupsFilePrintf(fp, ": %s%s", a->value->value, lf);
	}
      }
    }

    if (_cups_strcasecmp(g->name->value, "General"))
      cupsFilePrintf(fp, "*CloseGroup: %s%s", g->name->value, lf);
  }

  if (locales)
  {
    // Add localizations for additional languages...
    ppdcString	*locale;		// Locale name
    ppdcCatalog	*locatalog;		// Message catalog for locale


    // Write the translation strings for each language...
    for (locale = (ppdcString *)locales->first();
         locale;
	 locale = (ppdcString *)locales->next())
    {
      // Skip (US) English...
      if (!strcmp(locale->value, "en") || !strcmp(locale->value, "en_US"))
        continue;

      // Skip missing languages...
      if ((locatalog = src->find_po(locale->value)) == NULL)
        continue;

      // Do the core stuff first...
      cupsFilePrintf(fp, "*%s.Translation Manufacturer/%s: \"\"%s",
                     locale->value,
        	     locatalog->find_message(manufacturer->value), lf);

      if ((a = find_attr("ModelName", NULL)) != NULL)
	cupsFilePrintf(fp, "*%s.Translation ModelName/%s: \"\"%s",
                       locale->value,
        	       locatalog->find_message(a->value->value), lf);
      else if (_cups_strncasecmp(model_name->value, manufacturer->value,
                	   strlen(manufacturer->value)))
	cupsFilePrintf(fp, "*%s.Translation ModelName/%s %s: \"\"%s",
                       locale->value,
        	       locatalog->find_message(manufacturer->value),
        	       locatalog->find_message(model_name->value), lf);
      else
	cupsFilePrintf(fp, "*%s.Translation ModelName/%s: \"\"%s",
                       locale->value,
        	       locatalog->find_message(model_name->value), lf);

      if ((a = find_attr("ShortNickName", NULL)) != NULL)
	cupsFilePrintf(fp, "*%s.Translation ShortNickName/%s: \"\"%s",
                       locale->value,
        	       locatalog->find_message(a->value->value), lf);
      else if (_cups_strncasecmp(model_name->value, manufacturer->value,
                	   strlen(manufacturer->value)))
	cupsFilePrintf(fp, "*%s.Translation ShortNickName/%s %s: \"\"%s",
                       locale->value,
        	       locatalog->find_message(manufacturer->value),
        	       locatalog->find_message(model_name->value), lf);
      else
	cupsFilePrintf(fp, "*%s.Translation ShortNickName/%s: \"\"%s",
                       locale->value,
        	       locatalog->find_message(model_name->value), lf);

      if ((a = find_attr("NickName", NULL)) != NULL)
	cupsFilePrintf(fp, "*%s.Translation NickName/%s: \"\"%s",
                       locale->value,
        	       locatalog->find_message(a->value->value), lf);
      else if (_cups_strncasecmp(model_name->value, manufacturer->value,
                	   strlen(manufacturer->value)))
	cupsFilePrintf(fp, "*%s.Translation NickName/%s %s, %s: \"\"%s",
                       locale->value,
        	       locatalog->find_message(manufacturer->value),
        	       locatalog->find_message(model_name->value),
		       version->value, lf);
      else
	cupsFilePrintf(fp, "*%s.Translation NickName/%s, %s: \"\"%s",
                       locale->value,
        	       locatalog->find_message(model_name->value),
		       version->value, lf);

      // Then the page sizes...
      cupsFilePrintf(fp, "*%s.Translation PageSize/%s: \"\"%s", locale->value,
                     locatalog->find_message("Media Size"), lf);

      for (m = (ppdcMediaSize *)sizes->first();
	   m;
	   m = (ppdcMediaSize *)sizes->next())
      {
        cupsFilePrintf(fp, "*%s.PageSize %s/%s: \"\"%s", locale->value,
        	       m->name->value, locatalog->find_message(m->text->value),
		       lf);
      }

      // Next the groups and options...
      for (g = (ppdcGroup *)groups->first(); g; g = (ppdcGroup *)groups->next())
      {
	if (!g->options->count)
	  continue;

	if (_cups_strcasecmp(g->name->value, "General"))
	  cupsFilePrintf(fp, "*%s.Translation %s/%s: \"\"%s", locale->value,
	                 g->name->value,
                	 locatalog->find_message(g->text->value), lf);

	for (o = (ppdcOption *)g->options->first();
             o;
	     o = (ppdcOption *)g->options->next())
	{
	  if (!o->choices->count)
            continue;

          cupsFilePrintf(fp, "*%s.Translation %s/%s: \"\"%s", locale->value,
	                 o->name->value,
			 locatalog->find_message(o->text->value ?
			                         o->text->value :
						 o->name->value), lf);

	  for (c = (ppdcChoice *)o->choices->first();
               c;
	       c = (ppdcChoice *)o->choices->next())
	  {
            // Write this choice...
            cupsFilePrintf(fp, "*%s.%s %s/%s: \"\"%s", locale->value,
	                   o->name->value, c->name->value,
			   locatalog->find_message(c->text->value ?
			                           c->text->value :
						   c->name->value), lf);
	  }
	}
      }

      // Finally the localizable attributes...
      for (a = (ppdcAttr *)attrs->first(); a; a = (ppdcAttr *)attrs->next())
      {
        if (!a->localizable &&
	    (!a->text || !a->text->value || !a->text->value[0]) &&
	    strcmp(a->name->value, "APCustomColorMatchingName") &&
	    strcmp(a->name->value, "APPrinterPreset") &&
	    strcmp(a->name->value, "cupsICCProfile") &&
	    strcmp(a->name->value, "cupsIPPReason") &&
	    strcmp(a->name->value, "cupsMarkerName") &&
	    strncmp(a->name->value, "Custom", 6) &&
	    strncmp(a->name->value, "ParamCustom", 11))
	  continue;

        cupsFilePrintf(fp, "*%s.%s %s/%s: \"%s\"%s", locale->value,
	               a->name->value, a->selector->value,
		       locatalog->find_message(a->text && a->text->value ?
		                               a->text->value : a->name->value),
		       ((a->localizable && a->value->value[0]) ||
		        !strcmp(a->name->value, "cupsIPPReason")) ?
		           locatalog->find_message(a->value->value) : "",
		       lf);
      }
    }
  }

  if (default_font && default_font->value)
    cupsFilePrintf(fp, "*DefaultFont: %s%s", default_font->value, lf);
  else
    cupsFilePrintf(fp, "*DefaultFont: Courier%s", lf);

  for (fn = (ppdcFont *)fonts->first(); fn; fn = (ppdcFont *)fonts->next())
    if (!strcmp(fn->name->value, "*"))
    {
      for (bfn = (ppdcFont *)src->base_fonts->first();
	   bfn;
	   bfn = (ppdcFont *)src->base_fonts->next())
	cupsFilePrintf(fp, "*Font %s: %s \"%s\" %s %s%s",
		       bfn->name->value, bfn->encoding->value,
		       bfn->version->value, bfn->charset->value,
		       bfn->status == PPDC_FONT_ROM ? "ROM" : "Disk", lf);
    }
    else
      cupsFilePrintf(fp, "*Font %s: %s \"%s\" %s %s%s",
        	     fn->name->value, fn->encoding->value, fn->version->value,
		     fn->charset->value,
		     fn->status == PPDC_FONT_ROM ? "ROM" : "Disk", lf);

  cupsFilePrintf(fp, "*%% End of %s, %05d bytes.%s", pc_file_name->value,
        	 (int)((size_t)cupsFileTell(fp) + 25 + strlen(pc_file_name->value)),
		 lf);

  if (delete_cat)
    catalog->release();

  return (0);
}
