/* Copyright (C) 1999, 2000, 2001 artofcode LLC.  All rights reserved.
  
  This program 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 2 of the License, or (at your
  option) any later version.

  This program 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 this program; if not, write to the Free Software Foundation, Inc.,
  59 Temple Place, Suite 330, Boston, MA, 02111-1307.

*/

/*$Id: gdevpdfu.c,v 1.16.2.1.2.1 2003/01/17 00:49:01 giles Exp $ */
/* Output utilities for PDF-writing driver */
#include "memory_.h"
#include "jpeglib_.h"		/* for sdct.h */
#include "string_.h"
#include "gx.h"
#include "gserrors.h"
#include "gscdefs.h"
#include "gsdsrc.h"
#include "gsfunc.h"
#include "gdevpdfx.h"
#include "gdevpdfo.h"
#include "scanchar.h"
#include "strimpl.h"
#include "sa85x.h"
#include "scfx.h"
#include "sdct.h"
#include "slzwx.h"
#include "spngpx.h"
#include "srlx.h"
#include "sstring.h"
#include "szlibx.h"

/* Define the size of internal stream buffers. */
/* (This is not a limitation, it only affects performance.) */
#define sbuf_size 512

/* Optionally substitute other filters for FlateEncode for debugging. */
#if 1
#  define compression_filter_name "FlateDecode"
#  define compression_filter_template s_zlibE_template
#  define compression_filter_state stream_zlib_state
#else
#  include "slzwx.h"
#  define compression_filter_name "LZWDecode"
#  define compression_filter_template s_LZWE_template
#  define compression_filter_state stream_LZW_state
#endif

/* Import procedures for writing filter parameters. */
extern stream_state_proc_get_params(s_DCTE_get_params, stream_DCT_state);
extern stream_state_proc_get_params(s_CF_get_params, stream_CF_state);

#define CHECK(expr)\
  BEGIN if ((code = (expr)) < 0) return code; END

/* GC descriptors */
extern_st(st_pdf_font);
extern_st(st_pdf_char_proc);
extern_st(st_pdf_font_descriptor);
public_st_pdf_resource();
private_st_pdf_x_object();

/* ---------------- Utilities ---------------- */

/* ------ Document ------ */

/* Open the document if necessary. */
void
pdf_open_document(gx_device_pdf * pdev)
{
    if (!is_in_page(pdev) && pdf_stell(pdev) == 0) {
	stream *s = pdev->strm;
	int level = (int)(pdev->CompatibilityLevel * 10 + 0.5);

	pprintd2(s, "%%PDF-%d.%d\n", level / 10, level % 10);
	pdev->binary_ok = !pdev->params.ASCII85EncodePages;
	if (pdev->binary_ok)
	    stream_puts(s, "%\307\354\217\242\n");
    }
    /*
     * Determine the compression method.  Currently this does nothing.
     * It also isn't clear whether the compression method can now be
     * changed in the course of the document.
     *
     * The following algorithm is per an update to TN # 5151 by
     * Adobe Developer Support.
     */
    if (!pdev->params.CompressPages)
	pdev->compression = pdf_compress_none;
    else if (pdev->CompatibilityLevel < 1.2)
	pdev->compression = pdf_compress_LZW;
    else if (pdev->params.UseFlateCompression)
	pdev->compression = pdf_compress_Flate;
    else
	pdev->compression = pdf_compress_LZW;
}

/* ------ Objects ------ */

/* Allocate an object ID. */
private long
pdf_next_id(gx_device_pdf * pdev)
{
    return (pdev->next_id)++;
}

/*
 * Return the current position in the output.  Note that this may be in the
 * main output file, the asides file, or the pictures file.  If the current
 * file is the pictures file, positions returned by pdf_stell must only be
 * used locally (for computing lengths or patching), since there is no way
 * to map them later to the eventual position in the output file.
 */
long
pdf_stell(gx_device_pdf * pdev)
{
    stream *s = pdev->strm;
    long pos = stell(s);

    if (s == pdev->asides.strm)
	pos += ASIDES_BASE_POSITION;
    return pos;
}

/* Allocate an ID for a future object. */
long
pdf_obj_ref(gx_device_pdf * pdev)
{
    long id = pdf_next_id(pdev);
    long pos = pdf_stell(pdev);

    fwrite(&pos, sizeof(pos), 1, pdev->xref.file);
    return id;
}

/* Begin an object, optionally allocating an ID. */
long
pdf_open_obj(gx_device_pdf * pdev, long id)
{
    stream *s = pdev->strm;

    if (id <= 0) {
	id = pdf_obj_ref(pdev);
    } else {
	long pos = pdf_stell(pdev);
	FILE *tfile = pdev->xref.file;
	long tpos = ftell(tfile);

	fseek(tfile, (id - pdev->FirstObjectNumber) * sizeof(pos),
	      SEEK_SET);
	fwrite(&pos, sizeof(pos), 1, tfile);
	fseek(tfile, tpos, SEEK_SET);
    }
    pprintld1(s, "%ld 0 obj\n", id);
    return id;
}
long
pdf_begin_obj(gx_device_pdf * pdev)
{
    return pdf_open_obj(pdev, 0L);
}

/* End an object. */
int
pdf_end_obj(gx_device_pdf * pdev)
{
    stream_puts(pdev->strm, "endobj\n");
    return 0;
}

/* ------ Page contents ------ */

/* Handle transitions between contexts. */
private int
    none_to_stream(P1(gx_device_pdf *)), stream_to_text(P1(gx_device_pdf *)),
    string_to_text(P1(gx_device_pdf *)), text_to_stream(P1(gx_device_pdf *)),
    stream_to_none(P1(gx_device_pdf *));
typedef int (*context_proc) (P1(gx_device_pdf *));
private const context_proc context_procs[4][4] =
{
    {0, none_to_stream, none_to_stream, none_to_stream},
    {stream_to_none, 0, stream_to_text, stream_to_text},
    {text_to_stream, text_to_stream, 0, 0},
    {string_to_text, string_to_text, string_to_text, 0}
};

/* Enter stream context. */
private int
none_to_stream(gx_device_pdf * pdev)
{
    stream *s;

    if (pdev->contents_id != 0)
	return_error(gs_error_Fatal);	/* only 1 contents per page */
    pdev->contents_id = pdf_begin_obj(pdev);
    pdev->contents_length_id = pdf_obj_ref(pdev);
    s = pdev->strm;
    pprintld1(s, "<</Length %ld 0 R", pdev->contents_length_id);
    if (pdev->compression == pdf_compress_Flate)
	pprints1(s, "/Filter /%s", compression_filter_name);
    stream_puts(s, ">>\nstream\n");
    pdev->contents_pos = pdf_stell(pdev);
    if (pdev->compression == pdf_compress_Flate) {	/* Set up the Flate filter. */
	const stream_template *template = &compression_filter_template;
	stream *es = s_alloc(pdev->pdf_memory, "PDF compression stream");
	byte *buf = gs_alloc_bytes(pdev->pdf_memory, sbuf_size,
				   "PDF compression buffer");
	compression_filter_state *st =
	    gs_alloc_struct(pdev->pdf_memory, compression_filter_state,
			    template->stype, "PDF compression state");

	if (es == 0 || st == 0 || buf == 0)
	    return_error(gs_error_VMerror);
	s_std_init(es, buf, sbuf_size, &s_filter_write_procs,
		   s_mode_write);
	st->memory = pdev->pdf_memory;
	st->template = template;
	es->state = (stream_state *) st;
	es->procs.process = template->process;
	es->strm = s;
	(*template->set_defaults) ((stream_state *) st);
	(*template->init) ((stream_state *) st);
	pdev->strm = s = es;
    }
    /*
     * Scale the coordinate system.  Use an extra level of q/Q for the
     * sake of poorly designed PDF tools that assume that the contents
     * stream restores the CTM.
     */
    pprintg2(s, "q %g 0 0 %g 0 0 cm\n",
	     72.0 / pdev->HWResolution[0], 72.0 / pdev->HWResolution[1]);
    if (pdev->CompatibilityLevel >= 1.3) {
	/* Set the default rendering intent. */
	if (pdev->params.DefaultRenderingIntent != ri_Default) {
	    static const char *const ri_names[] = { psdf_ri_names };

	    pprints1(s, "/%s ri\n",
		     ri_names[(int)pdev->params.DefaultRenderingIntent]);
	}
    }
    /* Do a level of gsave for the clipping path. */
    stream_puts(s, "q\n");
    return PDF_IN_STREAM;
}
/* Enter text context from stream context. */
private int
stream_to_text(gx_device_pdf * pdev)
{
    /*
     * Bizarrely enough, Acrobat Reader cares how the final font size is
     * obtained -- the CTM (cm), text matrix (Tm), and font size (Tf)
     * are *not* all equivalent.  In particular, it seems to use the
     * product of the text matrix and font size to decide how to
     * anti-alias characters.  Therefore, we have to temporarily patch
     * the CTM so that the scale factors are unity.  What a nuisance!
     */
    pprintg2(pdev->strm, "q %g 0 0 %g 0 0 cm BT\n",
	     pdev->HWResolution[0] / 72.0, pdev->HWResolution[1] / 72.0);
    pdev->procsets |= Text;
    gs_make_identity(&pdev->text.matrix);
    pdev->text.line_start.x = pdev->text.line_start.y = 0;
    pdev->text.buffer_count = 0;
    return PDF_IN_TEXT;
}
/* Exit string context to text context. */
private int
string_to_text(gx_device_pdf * pdev)
{
    pdf_put_string(pdev, pdev->text.buffer, pdev->text.buffer_count);
    stream_puts(pdev->strm, (pdev->text.use_leading ? "'\n" : "Tj\n"));
    pdev->text.use_leading = false;
    pdev->text.buffer_count = 0;
    return PDF_IN_TEXT;
}
/* Exit text context to stream context. */
private int
text_to_stream(gx_device_pdf * pdev)
{
    stream_puts(pdev->strm, "ET Q\n");
    pdf_reset_text(pdev);	/* because of Q */
    return PDF_IN_STREAM;
}
/* Exit stream context. */
private int
stream_to_none(gx_device_pdf * pdev)
{
    stream *s = pdev->strm;
    long length;

    /* Close the extra q/Q for poorly designed PDF tools. */
    stream_puts(s, "Q\n");
    if (pdev->compression == pdf_compress_Flate) {	/* Terminate the Flate filter. */
	stream *fs = s->strm;

	sclose(s);
	gs_free_object(pdev->pdf_memory, s->cbuf, "zlib buffer");
	gs_free_object(pdev->pdf_memory, s, "zlib stream");
	pdev->strm = s = fs;
    }
    length = pdf_stell(pdev) - pdev->contents_pos;
    stream_puts(s, "endstream\n");
    pdf_end_obj(pdev);
    pdf_open_obj(pdev, pdev->contents_length_id);
    pprintld1(s, "%ld\n", length);
    pdf_end_obj(pdev);
    return PDF_IN_NONE;
}

/* Begin a page contents part. */
int
pdf_open_contents(gx_device_pdf * pdev, pdf_context_t context)
{
    int (*proc) (P1(gx_device_pdf *));

    while ((proc = context_procs[pdev->context][context]) != 0) {
	int code = (*proc) (pdev);

	if (code < 0)
	    return code;
	pdev->context = (pdf_context_t) code;
    }
    pdev->context = context;
    return 0;
}

/* Close the current contents part if we are in one. */
int
pdf_close_contents(gx_device_pdf * pdev, bool last)
{
    if (pdev->context == PDF_IN_NONE)
	return 0;
    if (last) {			/* Exit from the clipping path gsave. */
	pdf_open_contents(pdev, PDF_IN_STREAM);
	stream_puts(pdev->strm, "Q\n");
	pdev->text.font = 0;
    }
    return pdf_open_contents(pdev, PDF_IN_NONE);
}

/* ------ Resources et al ------ */

/* Define the allocator descriptors for the resource types. */
const char *const pdf_resource_type_names[] = {
    PDF_RESOURCE_TYPE_NAMES
};
const gs_memory_struct_type_t *const pdf_resource_type_structs[] = {
    PDF_RESOURCE_TYPE_STRUCTS
};

/* Find a resource of a given type by gs_id. */
pdf_resource_t *
pdf_find_resource_by_gs_id(gx_device_pdf * pdev, pdf_resource_type_t rtype,
			   gs_id rid)
{
    pdf_resource_t **pchain = PDF_RESOURCE_CHAIN(pdev, rtype, rid);
    pdf_resource_t **pprev = pchain;
    pdf_resource_t *pres;

    for (; (pres = *pprev) != 0; pprev = &pres->next)
	if (pres->rid == rid) {
	    if (pprev != pchain) {
		*pprev = pres->next;
		pres->next = *pchain;
		*pchain = pres;
	    }
	    return pres;
	}
    return 0;
}

/* Begin an object logically separate from the contents. */
long
pdf_open_separate(gx_device_pdf * pdev, long id)
{
    pdf_open_document(pdev);
    pdev->asides.save_strm = pdev->strm;
    pdev->strm = pdev->asides.strm;
    return pdf_open_obj(pdev, id);
}
long
pdf_begin_separate(gx_device_pdf * pdev)
{
    return pdf_open_separate(pdev, 0L);
}

/* Begin an aside (resource, annotation, ...). */
private int
pdf_alloc_aside(gx_device_pdf * pdev, pdf_resource_t ** plist,
		const gs_memory_struct_type_t * pst, pdf_resource_t **ppres,
		long id)
{
    pdf_resource_t *pres;
    cos_object_t *object;

    if (pst == NULL)
	pst = &st_pdf_resource;
    pres = gs_alloc_struct(pdev->pdf_memory, pdf_resource_t, pst,
			   "pdf_alloc_aside(resource)");
    object = cos_object_alloc(pdev, "pdf_alloc_aside(object)");
    if (pres == 0 || object == 0) {
	return_error(gs_error_VMerror);
    }
    if (id < 0) {
	object->id = -1L;
	pres->rname[0] = 0;
    } else {
	object->id = (id == 0 ? pdf_obj_ref(pdev) : id);
	sprintf(pres->rname, "R%ld", object->id);
    }
    pres->next = *plist;
    *plist = pres;
    pres->prev = pdev->last_resource;
    pdev->last_resource = pres;
    pres->named = false;
    pres->where_used = pdev->used_mask;
    pres->object = object;
    *ppres = pres;
    return 0;
}
int
pdf_begin_aside(gx_device_pdf * pdev, pdf_resource_t ** plist,
		const gs_memory_struct_type_t * pst, pdf_resource_t ** ppres)
{
    long id = pdf_begin_separate(pdev);

    if (id < 0)
	return (int)id;
    return pdf_alloc_aside(pdev, plist, pst, ppres, id);
}

/* Begin a resource of a given type. */
int
pdf_begin_resource_body(gx_device_pdf * pdev, pdf_resource_type_t rtype,
			gs_id rid, pdf_resource_t ** ppres)
{
    int code = pdf_begin_aside(pdev, PDF_RESOURCE_CHAIN(pdev, rtype, rid),
			       pdf_resource_type_structs[rtype], ppres);

    if (code >= 0)
	(*ppres)->rid = rid;
    return code;
}
int
pdf_begin_resource(gx_device_pdf * pdev, pdf_resource_type_t rtype, gs_id rid,
		   pdf_resource_t ** ppres)
{
    int code = pdf_begin_resource_body(pdev, rtype, rid, ppres);

    if (code >= 0 && pdf_resource_type_names[rtype] != 0) {
	stream *s = pdev->strm;

	pprints1(s, "<</Type%s", pdf_resource_type_names[rtype]);
	pprintld1(s, "/Name/R%ld", (*ppres)->object->id);
    }
    return code;
}

/* Allocate a resource, but don't open the stream. */
int
pdf_alloc_resource(gx_device_pdf * pdev, pdf_resource_type_t rtype, gs_id rid,
		   pdf_resource_t ** ppres, long id)
{
    int code = pdf_alloc_aside(pdev, PDF_RESOURCE_CHAIN(pdev, rtype, rid),
			       pdf_resource_type_structs[rtype], ppres, id);

    if (code >= 0)
	(*ppres)->rid = rid;
    return code;
}

/* Get the object id of a resource. */
long
pdf_resource_id(const pdf_resource_t *pres)
{
    return pres->object->id;
}

/* End an aside or other separate object. */
int
pdf_end_separate(gx_device_pdf * pdev)
{
    int code = pdf_end_obj(pdev);

    pdev->strm = pdev->asides.save_strm;
    pdev->asides.save_strm = 0;
    return code;
}
int
pdf_end_aside(gx_device_pdf * pdev)
{
    return pdf_end_separate(pdev);
}

/* End a resource. */
int
pdf_end_resource(gx_device_pdf * pdev)
{
    return pdf_end_aside(pdev);
}

/*
 * Write and release the Cos objects for resources local to a content stream.
 * We must write all the objects before freeing any of them, because
 * they might refer to each other.
 */
int
pdf_write_resource_objects(gx_device_pdf *pdev, pdf_resource_type_t rtype)
{
    int j;

    /* Write objects. */
    for (j = 0; j < NUM_RESOURCE_CHAINS; ++j) {
	pdf_resource_t *pres = pdev->resources[rtype].chains[j];

	for (; pres != 0; pres = pres->next)
	    if (!pres->named && !pres->object->written)
		cos_write_object(pres->object, pdev);
    }

    /* Free unnamed objects, which can't be used again. */
    for (j = 0; j < NUM_RESOURCE_CHAINS; ++j) {
	pdf_resource_t **prev = &pdev->resources[rtype].chains[j];
	pdf_resource_t *pres;

	while ((pres = *prev) != 0) {
	    if (pres->named) {	/* named, don't free */
		prev = &pres->next;
	    } else {
		cos_free(pres->object, "pdf_write_resource_objects");
		pres->object = 0;
		*prev = pres->next;
	    }
	}
    }

    return 0;
}

/*
 * Store the resource sets for a content stream (page or XObject).
 * Sets page->{procsets, resource_ids[]}.
 */
int
pdf_store_page_resources(gx_device_pdf *pdev, pdf_page_t *page)
{

    /* Write out any resource dictionaries. */

    {
	int i;

	for (i = 0; i <= resourceFont; ++i) {
	    stream *s = 0;
	    int j;

	    page->resource_ids[i] = 0;
	    for (j = 0; j < NUM_RESOURCE_CHAINS; ++j) {
		pdf_resource_t *pres = pdev->resources[i].chains[j];

		for (; pres != 0; pres = pres->next) {
		    if (pres->where_used & pdev->used_mask) {
			long id = pres->object->id;

			if (s == 0) {
			    page->resource_ids[i] = pdf_begin_separate(pdev);
			    s = pdev->strm;
			    stream_puts(s, "<<");
			}
			pprints1(s, "/%s\n", pres->rname);
			pprintld1(s, "%ld 0 R", id);
			pres->where_used -= pdev->used_mask;
		    }
		}
	    }
	    if (s) {
		stream_puts(s, ">>\n");
		pdf_end_separate(pdev);
		if (i != resourceFont)
		    pdf_write_resource_objects(pdev, i);
	    }
	}
    }

    page->procsets = pdev->procsets;
    return 0;
}

/* Copy data from a temporary file to a stream. */
void
pdf_copy_data(stream *s, FILE *file, long count)
{
    long left = count;
    byte buf[sbuf_size];

    while (left > 0) {
	uint copy = min(left, sbuf_size);

	fread(buf, 1, sbuf_size, file);
	stream_write(s, buf, copy);
	left -= copy;
    }
}

/* ------ Pages ------ */

/* Get or assign the ID for a page. */
/* Returns 0 if the page number is out of range. */
long
pdf_page_id(gx_device_pdf * pdev, int page_num)
{
    cos_dict_t *Page;

    if (page_num < 1)
	return 0;
    if (page_num >= pdev->num_pages) {	/* Grow the pages array. */
	uint new_num_pages =
	    max(page_num + 10, pdev->num_pages << 1);
	pdf_page_t *new_pages =
	    gs_resize_object(pdev->pdf_memory, pdev->pages, new_num_pages,
			     "pdf_page_id(resize pages)");

	if (new_pages == 0)
	    return 0;
	memset(&new_pages[pdev->num_pages], 0,
	       (new_num_pages - pdev->num_pages) * sizeof(pdf_page_t));
	pdev->pages = new_pages;
	pdev->num_pages = new_num_pages;
    }
    if ((Page = pdev->pages[page_num - 1].Page) == 0) {
	pdev->pages[page_num - 1].Page = Page =
	    cos_dict_alloc(pdev, "pdf_page_id");
	Page->id = pdf_obj_ref(pdev);
    }
    return Page->id;
}

/* Get the page structure for the current page. */
pdf_page_t *
pdf_current_page(gx_device_pdf *pdev)
{
    return &pdev->pages[pdev->next_page];
}

/* Get the dictionary object for the current page. */
cos_dict_t *
pdf_current_page_dict(gx_device_pdf *pdev)
{
    if (pdf_page_id(pdev, pdev->next_page + 1) <= 0)
	return 0;
    return pdev->pages[pdev->next_page].Page;
}

/* Write saved page- or document-level information. */
int
pdf_write_saved_string(gx_device_pdf * pdev, gs_string * pstr)
{
    if (pstr->data != 0) {
	stream_write(pdev->strm, pstr->data, pstr->size);
	gs_free_string(pdev->pdf_memory, pstr->data, pstr->size,
		       "pdf_write_saved_string");
	pstr->data = 0;
    }
    return 0;
}

/* Open a page for writing. */
int
pdf_open_page(gx_device_pdf * pdev, pdf_context_t context)
{
    if (!is_in_page(pdev)) {
	if (pdf_page_id(pdev, pdev->next_page + 1) == 0)
	    return_error(gs_error_VMerror);
	pdf_open_document(pdev);
    }
    /* Note that context may be PDF_IN_NONE here. */
    return pdf_open_contents(pdev, context);
}

/* ------ Miscellaneous output ------ */

/* Generate the default Producer string. */
void
pdf_store_default_Producer(char buf[PDF_MAX_PRODUCER])
{
    sprintf(buf, ((gs_revision % 100) == 0 ? "(%s %1.1f)" : "(%s %1.2f)"),
	    gs_product, gs_revision / 100.0);
}

/* Write matrix values. */
void
pdf_put_matrix(gx_device_pdf * pdev, const char *before,
	       const gs_matrix * pmat, const char *after)
{
    stream *s = pdev->strm;

    if (before)
	stream_puts(s, before);
    pprintg6(s, "%g %g %g %g %g %g ",
	     pmat->xx, pmat->xy, pmat->yx, pmat->yy, pmat->tx, pmat->ty);
    if (after)
	stream_puts(s, after);
}

/*
 * Write a name, with escapes for unusual characters.  In PDF 1.1, we have
 * no choice but to replace these characters with '?'; in PDF 1.2, we can
 * use an escape sequence for anything except a null <00>.
 */
private int
pdf_put_name_chars_1_1(stream *s, const byte *nstr, uint size)
{
    uint i;

    for (i = 0; i < size; ++i) {
	uint c = nstr[i];

	switch (c) {
	    default:
		if (c >= 0x21 && c <= 0x7e) {
		    stream_putc(s, c);
		    break;
		}
		/* falls through */
	    case '%':
	    case '(': case ')':
	    case '<': case '>':
	    case '[': case ']':
	    case '{': case '}':
	    case '/':
	    case 0:
		stream_putc(s, '?');
	}
    }
    return 0;
}
private int
pdf_put_name_chars_1_2(stream *s, const byte *nstr, uint size)
{
    uint i;

    for (i = 0; i < size; ++i) {
	uint c = nstr[i];
	char hex[4];

	switch (c) {
	    default:
		if (c >= 0x21 && c <= 0x7e) {
		    stream_putc(s, c);
		    break;
		}
		/* falls through */
	    case '#':
	    case '%':
	    case '(': case ')':
	    case '<': case '>':
	    case '[': case ']':
	    case '{': case '}':
	    case '/':
		sprintf(hex, "#%02x", c);
		stream_puts(s, hex);
		break;
	    case 0:
		stream_putc(s, '?');
	}
    }
    return 0;
}
pdf_put_name_chars_proc_t
pdf_put_name_chars_proc(const gx_device_pdf *pdev)
{
    return (pdev->CompatibilityLevel >= 1.2 ? pdf_put_name_chars_1_2 :
	    pdf_put_name_chars_1_1);
}
void
pdf_put_name_chars(const gx_device_pdf *pdev, const byte *nstr, uint size)
{
    DISCARD(pdf_put_name_chars_proc(pdev)(pdev->strm, nstr, size));
}
void
pdf_put_name(const gx_device_pdf *pdev, const byte *nstr, uint size)
{
    stream_putc(pdev->strm, '/');
    pdf_put_name_chars(pdev, nstr, size);
}

/*
 * Write a string in its shortest form ( () or <> ).  Note that
 * this form is different depending on whether binary data are allowed.
 * We wish PDF supported ASCII85 strings ( <~ ~> ), but it doesn't.
 */
void
pdf_put_string(const gx_device_pdf * pdev, const byte * str, uint size)
{
    psdf_write_string(pdev->strm, str, size,
		      (pdev->binary_ok ? PRINT_BINARY_OK : 0));
}

/* Write a value, treating names specially. */
void
pdf_write_value(const gx_device_pdf * pdev, const byte * vstr, uint size)
{
    if (size > 0 && vstr[0] == '/')
	pdf_put_name(pdev, vstr + 1, size - 1);
    else
	stream_write(pdev->strm, vstr, size);
}

/* Store filters for a stream. */
/* Currently this only saves parameters for CCITTFaxDecode. */
int
pdf_put_filters(cos_dict_t *pcd, gx_device_pdf *pdev, stream *s,
		const pdf_filter_names_t *pfn)
{
    const char *filter_name = 0;
    bool binary_ok = true;
    stream *fs = s;
    cos_dict_t *decode_parms = 0;
    int code;

    for (; fs != 0; fs = fs->strm) {
	const stream_state *st = fs->state;
	const stream_template *template = st->template;

#define TEMPLATE_IS(atemp)\
  (template->process == (atemp).process)
	if (TEMPLATE_IS(s_A85E_template))
	    binary_ok = false;
	else if (TEMPLATE_IS(s_CFE_template)) {
	    cos_param_list_writer_t writer;
	    stream_CF_state cfs;

	    decode_parms =
		cos_dict_alloc(pdev, "pdf_put_image_filters(decode_parms)");
	    if (decode_parms == 0)
		return_error(gs_error_VMerror);
	    CHECK(cos_param_list_writer_init(&writer, decode_parms, 0));
	    /*
	     * If EndOfBlock is true, we mustn't write out a Rows value.
	     * This is a hack....
	     */
	    cfs = *(const stream_CF_state *)st;
	    if (cfs.EndOfBlock)
		cfs.Rows = 0;
	    CHECK(s_CF_get_params((gs_param_list *)&writer, &cfs, false));
	    filter_name = pfn->CCITTFaxDecode;
	} else if (TEMPLATE_IS(s_DCTE_template))
	    filter_name = pfn->DCTDecode;
	else if (TEMPLATE_IS(s_zlibE_template))
	    filter_name = pfn->FlateDecode;
	else if (TEMPLATE_IS(s_LZWE_template))
	    filter_name = pfn->LZWDecode;
	else if (TEMPLATE_IS(s_PNGPE_template)) {
	    /* This is a predictor for FlateDecode or LZWEncode. */
	    const stream_PNGP_state *const ss =
		(const stream_PNGP_state *)st;

	    decode_parms =
		cos_dict_alloc(pdev, "pdf_put_image_filters(decode_parms)");
	    if (decode_parms == 0)
		return_error(gs_error_VMerror);
	    CHECK(cos_dict_put_c_key_int(decode_parms, "/Predictor",
					 ss->Predictor));
	    CHECK(cos_dict_put_c_key_int(decode_parms, "/Columns",
					 ss->Columns));
	    if (ss->Colors != 1)
		CHECK(cos_dict_put_c_key_int(decode_parms, "/Colors",
					     ss->Colors));
	    if (ss->BitsPerComponent != 8)
		CHECK(cos_dict_put_c_key_int(decode_parms,
					     "/BitsPerComponent",
					     ss->BitsPerComponent));
	} else if (TEMPLATE_IS(s_RLE_template))
	    filter_name = pfn->RunLengthDecode;
#undef TEMPLATE_IS
    }
    if (filter_name) {
	if (binary_ok) {
	    CHECK(cos_dict_put_c_strings(pcd, pfn->Filter, filter_name));
	    if (decode_parms)
		CHECK(cos_dict_put_c_key_object(pcd, pfn->DecodeParms,
						COS_OBJECT(decode_parms)));
	} else {
	    cos_array_t *pca =
		cos_array_alloc(pdev, "pdf_put_image_filters(Filters)");

	    if (pca == 0)
		return_error(gs_error_VMerror);
	    CHECK(cos_array_add_c_string(pca, pfn->ASCII85Decode));
	    CHECK(cos_array_add_c_string(pca, filter_name));
	    CHECK(cos_dict_put_c_key_object(pcd, pfn->Filter,
					    COS_OBJECT(pca)));
	    if (decode_parms) {
		pca = cos_array_alloc(pdev,
				      "pdf_put_image_filters(DecodeParms)");
		if (pca == 0)
		    return_error(gs_error_VMerror);
		CHECK(cos_array_add_c_string(pca, "null"));
		CHECK(cos_array_add_object(pca, COS_OBJECT(decode_parms)));
		CHECK(cos_dict_put_c_key_object(pcd, pfn->DecodeParms,
						COS_OBJECT(pca)));
	    }
	}
    } else if (!binary_ok)
	CHECK(cos_dict_put_c_strings(pcd, pfn->Filter, pfn->ASCII85Decode));
    return 0;
}

/* Add a Flate compression filter to a binary writer. */
private int
pdf_flate_binary(gx_device_pdf *pdev, psdf_binary_writer *pbw)
{
    const stream_template *template = &s_zlibE_template;
    stream_state *st = s_alloc_state(pdev->pdf_memory, template->stype,
				     "pdf_write_function");

    if (st == 0)
	return_error(gs_error_VMerror);
    if (template->set_defaults)
	template->set_defaults(st);
    return psdf_encode_binary(pbw, template, st);
}

/*
 * Begin a data stream.  The client has opened the object and written
 * the << and any desired dictionary keys.
 */
int
pdf_begin_data_binary(gx_device_pdf *pdev, pdf_data_writer_t *pdw,
		      bool data_is_binary)
{
    long length_id = pdf_obj_ref(pdev);
    stream *s = pdev->strm;
#define USE_ASCII85 1
#define USE_FLATE 2
    static const char *const fnames[4] = {
	"", "/Filter/ASCII85Decode", "/Filter/FlateDecode",
	"/Filter[/ASCII85Decode/FlateDecode]"
    };
    int filters = 0;
    int code;

    if (pdev->CompatibilityLevel >= 1.2) {
	filters |= USE_FLATE;
	data_is_binary = true;
    }
    if (data_is_binary && !pdev->binary_ok)
	filters |= USE_ASCII85;
    stream_puts(s, fnames[filters]);
    pprintld1(s, "/Length %ld 0 R>>stream\n", length_id);
    code = psdf_begin_binary((gx_device_psdf *)pdev, &pdw->binary);
    if (code < 0)
	return code;
    pdw->start = stell(s);
    pdw->length_id = length_id;
    if (filters & USE_FLATE)
	code = pdf_flate_binary(pdev, &pdw->binary);
    return code;
#undef USE_ASCII85
#undef USE_FLATE
}

/* End a data stream. */
int
pdf_end_data(pdf_data_writer_t *pdw)
{
    gx_device_pdf *pdev = (gx_device_pdf *)pdw->binary.dev;
    int code = psdf_end_binary(&pdw->binary);
    long length = stell(pdev->strm) - pdw->start;

    if (code < 0)
	return code;
    stream_puts(pdev->strm, "\nendstream\n");
    pdf_end_separate(pdev);
    pdf_open_separate(pdev, pdw->length_id);
    pprintld1(pdev->strm, "%ld\n", length);
    return pdf_end_separate(pdev);
}

/* Create a Function object. */
int
pdf_function(gx_device_pdf *pdev, const gs_function_t *pfn,
	     cos_value_t *pvalue)
{
    gs_function_info_t info;
    cos_param_list_writer_t rlist;
    pdf_resource_t *pres;
    cos_object_t *pcfn;
    cos_dict_t *pcd;
    cos_value_t v;
    int code = pdf_alloc_resource(pdev, resourceFunction, gs_no_id, &pres, 0L);

    if (code < 0)
	return code;
    pcfn = pres->object;
    gs_function_get_info(pfn, &info);
    if (info.DataSource != 0) {
	psdf_binary_writer writer;
	stream *save = pdev->strm;
	cos_stream_t *pcos;
	stream *s;

	cos_become(pcfn, cos_type_stream);
	pcos = (cos_stream_t *)pcfn;
	pcd = cos_stream_dict(pcos);
	s = cos_write_stream_alloc(pcos, pdev, "pdf_function");
	if (s == 0)
	    return_error(gs_error_VMerror);
	pdev->strm = s;
	code = psdf_begin_binary((gx_device_psdf *)pdev, &writer);
	if (code >= 0 && info.data_size > 30 &&	/* 30 is arbitrary */
	    pdev->CompatibilityLevel >= 1.2
	    )
	    code = pdf_flate_binary(pdev, &writer);
	if (code >= 0) {
	    static const pdf_filter_names_t fnames = {
		PDF_FILTER_NAMES
	    };

	    code = pdf_put_filters(pcd, pdev, writer.strm, &fnames);
	}
	if (code >= 0) {
	    byte buf[100];		/* arbitrary */
	    ulong pos;
	    uint count;
	    const byte *ptr;

	    for (pos = 0; pos < info.data_size; pos += count) {
		count = min(sizeof(buf), info.data_size - pos);
		data_source_access_only(info.DataSource, pos, count, buf,
					&ptr);
		stream_write(writer.strm, ptr, count);
	    }
	    code = psdf_end_binary(&writer);
	    sclose(s);
	}
	pdev->strm = save;
	if (code < 0)
	    return code;
    } else {
	cos_become(pcfn, cos_type_dict);
	pcd = (cos_dict_t *)pcfn;
    }
    if (info.Functions != 0) {
	int i;
	cos_array_t *functions =
	    cos_array_alloc(pdev, "pdf_function(Functions)");

	if (functions == 0)
	    return_error(gs_error_VMerror);
	for (i = 0; i < info.num_Functions; ++i) {
	    if ((code = pdf_function(pdev, info.Functions[i], &v)) < 0 ||
		(code = cos_array_add(functions, &v)) < 0
		) {
		COS_FREE(functions, "pdf_function(Functions)");
		return code;
	    }
	}
	code = cos_dict_put_c_key(pcd, "/Functions",
				  COS_OBJECT_VALUE(&v, functions));
	if (code < 0) {
	    COS_FREE(functions, "pdf_function(Functions)");
	    return code;
	}
    }
    code = cos_param_list_writer_init(&rlist, pcd, PRINT_BINARY_OK);
    if (code < 0)
	return code;
    code = gs_function_get_params(pfn, (gs_param_list *)&rlist);
    if (code < 0)
	return code;
    COS_OBJECT_VALUE(pvalue, pcd);
    return 0;
}

/* Write a Function object. */
int
pdf_write_function(gx_device_pdf *pdev, const gs_function_t *pfn, long *pid)
{
    cos_value_t value;
    int code = pdf_function(pdev, pfn, &value);

    if (code < 0)
	return code;
    *pid = value.contents.object->id;
    return 0;
}
