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