/* Copyright (C) 2020-2022 Artifex Software, Inc. All Rights Reserved. This software is provided AS-IS with no warranty, either express or implied. This software is distributed under license and may not be copied, modified or distributed except as expressly authorized under the terms of the license contained in the file LICENSE in this distribution. Refer to licensing information at http://www.artifex.com or contact Artifex Software, Inc., 1305 Grant Avenue - Suite 200, Novato, CA 94945, U.S.A., +1(415)492-9861, for further information. */ /* pdfmark handling for the PDF interpreter */ #include "pdf_int.h" #include "pdf_stack.h" #include "pdf_file.h" #include "pdf_dict.h" #include "pdf_array.h" #include "pdf_loop_detect.h" #include "pdf_mark.h" #include "pdf_obj.h" #include "pdf_misc.h" #include "pdf_page.h" #include "pdf_deref.h" #include "gscoord.h" /* For gs_currentmatrix */ static int pdfi_pdfmark_setparam_obj(pdf_context *ctx, pdf_obj *obj, gs_param_string *entry) { int code = 0; byte *data = NULL; int size = 0; code = pdfi_obj_to_string(ctx, obj, &data, &size); if (code < 0) return code; entry->data = data; entry->size = size; entry->persistent = false; return 0; } static int pdfi_pdfmark_setparam_pair(pdf_context *ctx, pdf_name *Key, pdf_obj *Value, gs_param_string *entry) { int code = 0; /* Handle the Key */ if (pdfi_type_of(Key) != PDF_NAME) { code = gs_note_error(gs_error_typecheck); goto exit; } code = pdfi_pdfmark_setparam_obj(ctx, (pdf_obj *)Key, entry); if (code < 0) goto exit; code = pdfi_pdfmark_setparam_obj(ctx, Value, entry+1); if (code < 0) goto exit; exit: return code; } /* Note: this isn't part of the obj_to_string() stuff */ static int pdfi_pdfmark_ctm_str(pdf_context *ctx, gs_matrix *ctm, byte **data, int *len) { int size = 100; char *buf; buf = (char *)gs_alloc_bytes(ctx->memory, size, "pdfi_pdfmark_ctm_str(data)"); if (buf == NULL) return_error(gs_error_VMerror); snprintf(buf, size, "[%.4f %.4f %.4f %.4f %.4f %.4f]", ctm->xx, ctm->xy, ctm->yx, ctm->yy, ctm->tx, ctm->ty); *data = (byte *)buf; *len = strlen(buf); return 0; } /* Write an string array command to the device (e.g. pdfmark) */ static int pdfi_pdfmark_write_array(pdf_context *ctx, gs_param_string_array *array_list, const char *command) { gs_c_param_list list; int code; /* Set the list to writeable, and initialise it */ gs_c_param_list_write(&list, ctx->memory); /* We don't want keys to be persistent, as we are going to throw * away our array, force them to be copied */ gs_param_list_set_persistent_keys((gs_param_list *) &list, false); /* Make really sure the list is writable, but don't initialise it */ gs_c_param_list_write_more(&list); /* Add the param string array to the list */ code = param_write_string_array((gs_param_list *)&list, command, array_list); if (code < 0) return code; /* Set the param list back to readable, so putceviceparams can readit (mad...) */ gs_c_param_list_read(&list); /* and set the actual device parameters */ code = gs_putdeviceparams(ctx->pgs->device, (gs_param_list *)&list); gs_c_param_list_release(&list); return code; } /* Write an string array command to the device (e.g. pdfmark) */ static int pdfi_pdfmark_write_string(pdf_context *ctx, gs_param_string *param_string, const char *command) { gs_c_param_list list; int code; /* Set the list to writeable, and initialise it */ gs_c_param_list_write(&list, ctx->memory); /* We don't want keys to be persistent, as we are going to throw * away our array, force them to be copied */ gs_param_list_set_persistent_keys((gs_param_list *) &list, false); /* Make really sure the list is writable, but don't initialise it */ gs_c_param_list_write_more(&list); /* Add the param string array to the list */ code = param_write_string((gs_param_list *)&list, command, param_string); if (code < 0) return code; /* Set the param list back to readable, so putceviceparams can readit (mad...) */ gs_c_param_list_read(&list); /* and set the actual device parameters */ code = gs_putdeviceparams(ctx->pgs->device, (gs_param_list *)&list); gs_c_param_list_release(&list); return code; } /* Apparently the strings to pass to the device are: key1 val1 .... keyN valN CTM "ANN" CTM is (for example) "[1.0 0 0 1.0 0 0]" This takes an optional 'label' argument which can be NULL. the /pdfmark command has this array of strings as a parameter, i.e. [ 'label' key1 val1 .... keyN valN CTM "ANN" ] /pdfmark Apparently the "type" doesn't have a leading "/" but the other names in the keys do need the "/" See plparams.c/process_pdfmark() */ static int pdfi_pdfmark_from_dict_withlabel(pdf_context *ctx, pdf_indirect_ref *label, pdf_dict *dict, gs_matrix *ctm, const char *type) { int code = 0; int size; uint64_t dictsize; uint64_t index; uint64_t keynum = 0; int i; pdf_name *Key = NULL; pdf_obj *Value = NULL; pdf_obj *tempobj = NULL; gs_param_string *parray = NULL; gs_param_string_array array_list; byte *ctm_data = NULL; int ctm_len; gs_matrix ctm_placeholder; int offset = 0; /* If ctm not provided, make a placeholder */ if (!ctm) { gs_currentmatrix(ctx->pgs, &ctm_placeholder); ctm = &ctm_placeholder; } dictsize = pdfi_dict_entries(dict); size = dictsize*2 + 2; /* pairs + CTM + type */ if (label) size += 1; parray = (gs_param_string *)gs_alloc_bytes(ctx->memory, size*sizeof(gs_param_string), "pdfi_pdfmark_from_dict(parray)"); if (parray == NULL) { code = gs_note_error(gs_error_VMerror); goto exit; } memset(parray, 0, size*sizeof(gs_param_string)); if (label) { code = pdfi_pdfmark_setparam_obj(ctx, (pdf_obj *)label, parray+0); offset += 1; } /* Get each (key,val) pair from dict and setup param for it */ if (dictsize > 0) { code = pdfi_dict_key_first(ctx, dict, (pdf_obj **)&Key, &index); while (code >= 0) { code = pdfi_dict_get_no_deref(ctx, dict, Key, &Value); if (code < 0) goto exit; code = pdfi_pdfmark_setparam_pair(ctx, Key, Value, parray+offset+(keynum*2)); if (code < 0) goto exit; pdfi_countdown(Key); Key = NULL; pdfi_countdown(Value); Value = NULL; pdfi_countdown(tempobj); tempobj = NULL; code = pdfi_dict_key_next(ctx, dict, (pdf_obj **)&Key, &index); if (code == gs_error_undefined) { code = 0; break; } keynum ++; } } if (code < 0) goto exit; /* CTM */ code = pdfi_pdfmark_ctm_str(ctx, ctm, &ctm_data, &ctm_len); if (code < 0) goto exit; parray[size-2].data = ctm_data; parray[size-2].size = ctm_len; /* Type (e.g. ANN, DOCINFO) */ parray[size-1].data = (const byte *)type; parray[size-1].size = strlen(type); array_list.data = parray; array_list.persistent = false; array_list.size = size; code = pdfi_pdfmark_write_array(ctx, &array_list, "pdfmark"); exit: pdfi_countdown(Key); pdfi_countdown(Value); pdfi_countdown(tempobj); if (parray != NULL) { /* Free the param data except the last two which are handled separately */ for (i=0; imemory, (byte *)parray[i].data, "pdfi_pdfmark_from_dict(parray)"); } } if (ctm_data) gs_free_object(ctx->memory, ctm_data, "pdfi_pdfmark_from_dict(ctm_data)"); gs_free_object(ctx->memory, parray, "pdfi_pdfmark_from_dict(parray)"); return code; } /* Do a pdfmark from a dictionary */ int pdfi_pdfmark_from_dict(pdf_context *ctx, pdf_dict *dict, gs_matrix *ctm, const char *type) { return pdfi_pdfmark_from_dict_withlabel(ctx, NULL, dict, ctm, type); } /* Does a pdfmark, from a c-array of pdf_obj's * This will put in a dummy ctm if none provided */ int pdfi_pdfmark_from_objarray(pdf_context *ctx, pdf_obj **objarray, int len, gs_matrix *ctm, const char *type) { int code = 0; int size; gs_param_string *parray = NULL; gs_param_string_array array_list; byte *ctm_data = NULL; int ctm_len; gs_matrix ctm_placeholder; int i; /* If ctm not provided, make a placeholder */ if (!ctm) { gs_currentmatrix(ctx->pgs, &ctm_placeholder); ctm = &ctm_placeholder; } size = len + 2; /* data + CTM + type */ parray = (gs_param_string *)gs_alloc_bytes(ctx->memory, size*sizeof(gs_param_string), "pdfi_pdfmark_from_objarray(parray)"); if (parray == NULL) { code = gs_note_error(gs_error_VMerror); goto exit; } memset(parray, 0, size *sizeof(gs_param_string)); for (i=0; imemory, (byte *)parray[i].data, "pdfi_pdfmark_from_objarray(parray)"); } } if (ctm_data) gs_free_object(ctx->memory, ctm_data, "pdfi_pdfmark_from_objarray(ctm_data)"); gs_free_object(ctx->memory, parray, "pdfi_pdfmark_from_objarray(parray)"); return code; } /* Send an arbitrary object as a string, with command 'cmd' * This is not a pdfmark, has no ctm. */ int pdfi_pdfmark_object(pdf_context *ctx, pdf_obj *object, const char *cmd) { gs_param_string param_string; int code = 0; param_string.data = NULL; code = pdfi_loop_detector_mark(ctx); if (code < 0) goto exit; code = pdfi_resolve_indirect_loop_detect(ctx, NULL, object, true); (void)pdfi_loop_detector_cleartomark(ctx); if (code < 0) goto exit; code = pdfi_pdfmark_setparam_obj(ctx, object, ¶m_string); if (code < 0) goto exit; code = pdfi_pdfmark_write_string(ctx, ¶m_string, cmd); exit: if (param_string.data != NULL) gs_free_object(ctx->memory, (byte *)param_string.data, "free data transferred to param_string in pdfi_pdfmark_object\n"); return code; } /* Convert a Dest array to the hacky Page and View keys that pdfwrite expects * dest_array: [ ] * page_ref: indirect ref to a page dict * view_info: see table 8.2 in PDF 1.7, for example "/Fit" * * Removes /Dest and inserts two key pairs: /Page N and /View * N is the page number, which starts at 1, not 0. */ static int pdfi_pdfmark_add_Page_View(pdf_context *ctx, pdf_dict *link_dict, pdf_array *dest_array) { int code = 0; int i; uint64_t page_num; pdf_dict *page_dict = NULL; pdf_array *view_array = NULL; uint64_t array_size; pdf_obj *temp_obj = NULL; /* Get the page_dict, without storing the deref in the array. * (This is needed because otherwise there could be a circular ref that will * lead to a memory leak) */ code = pdfi_array_get_no_store_R(ctx, dest_array, 0, (pdf_obj **)&page_dict); if (code < 0) goto exit; if(pdfi_type_of(page_dict) == PDF_INT) { page_num = ((pdf_num *)page_dict)->value.i; } else { if (pdfi_type_of(page_dict) != PDF_DICT) { code = gs_note_error(gs_error_typecheck); goto exit; } /* Find out which page number this is */ code = pdfi_page_get_number(ctx, page_dict, &page_num); if (code < 0) goto exit; } page_num += ctx->Pdfmark_InitialPage; /* Add /Page key to the link_dict * Of course pdfwrite is numbering its pages starting at 1, because... of course :( */ code = pdfi_dict_put_int(ctx, link_dict, "Page", page_num+1); /* Build an array for /View, out of the remainder of the Dest entry */ array_size = pdfi_array_size(dest_array) - 1; code = pdfi_array_alloc(ctx, array_size, &view_array); if (code < 0) goto exit; pdfi_countup(view_array); for (i=0; iRoot, "Dests", PDF_DICT, (pdf_obj **)&Dests); if (code < 0) goto exit; code = pdfi_dict_knownget_type(ctx, ctx->Root, "Names", PDF_DICT, (pdf_obj **)&Names_dict); if (code < 0) goto exit; switch (pdfi_type_of(Dest)) { case PDF_ARRAY: code = pdfi_pdfmark_add_Page_View(ctx, link_dict, (pdf_array *)Dest); if (code < 0) goto exit; break; case PDF_NAME: if (Dests != NULL) { /* Case where it's a name to look up in Contents(Root) /Dests */ code = pdfi_dict_get_by_key(ctx, Dests, (const pdf_name *)Dest, (pdf_obj **)&dest_array); if (code == gs_error_undefined) { /* TODO: Not found, should flag a warning */ code = 0; goto exit; } if (code < 0) goto exit; if (pdfi_type_of(dest_array) != PDF_ARRAY) { code = gs_note_error(gs_error_typecheck); goto exit; } code = pdfi_pdfmark_add_Page_View(ctx, link_dict, dest_array); if (code < 0) goto exit; break; } /* fallthrough */ case PDF_STRING: if (Names_dict != NULL) { /* Looking in Catalog(Root) for /Names<>>> */ code = pdfi_dict_knownget_type(ctx, Names_dict, "Dests", PDF_DICT, (pdf_obj **)&Dests); if (code < 0) goto exit; if (code == 0) { /* TODO: Not found -- not sure if there is another case here or not */ goto exit; } code = pdfi_dict_knownget_type(ctx, Dests, "Names", PDF_ARRAY, (pdf_obj **)&Names); if (code < 0) goto exit; if (code == 0) { /* TODO: Not found -- not sure if there is another case here or not */ goto exit; } code = pdfi_pdfmark_handle_dest_names(ctx, link_dict, Dest, Names); if (code < 0) goto exit; } else { /* TODO: Ignore it -- flag a warning? */ } break; default: /* TODO: Ignore it -- flag a warning? */ break; } exit: if (delete_Dest) { /* Delete the Dest key */ code1 = pdfi_dict_delete(ctx, link_dict, "Dest"); if (code1 < 0 && code >= 0) code = code1; } pdfi_countdown(Dest); pdfi_countdown(Dests); pdfi_countdown(Names); pdfi_countdown(Names_dict); pdfi_countdown(dest_array); return code; } static int pdfi_check_limits(pdf_context *ctx, pdf_dict *node, char *str, int len) { int code = 0, min, i, len2 = 0; pdf_array *Limits = NULL; pdf_string *Str = NULL; char *str2 = NULL; code = pdfi_dict_get_type(ctx, node, "Limits", PDF_ARRAY, (pdf_obj **)&Limits); if (code < 0) goto error; if (pdfi_array_size(Limits) != 2) { /* Limits are not valid, just ignore them. The calling code will then check * the Names array. */ pdfi_set_warning(ctx, 0, NULL, PDF_W_BAD_TREE_LIMITS, "pdfi_get_name_from_node", 0); goto error; } code = pdfi_array_get_type(ctx, Limits, 0, PDF_STRING, (pdf_obj **)&Str); if (code < 0) goto error; if (pdfi_type_of(Str) == PDF_NAME) { code = pdfi_string_from_name(ctx, (pdf_name *)Str, &str2, &len2); if (code < 0) return code; } else { len2 = ((pdf_string *)Str)->length; str2 = (char *)gs_alloc_bytes(ctx->memory, len2 + 1, "pdfi_get_named_dest"); if (str2 == NULL) { code = gs_note_error(gs_error_VMerror); goto error; } memcpy(str2, ((pdf_string *)Str)->data, len2); str2[len2] = 0; } pdfi_countdown(Str); Str = NULL; min = len; if (len2 < min) min = len2; for (i=0;i< min;i++) { if (str[i] < str2[i]) { code = gs_note_error(gs_error_undefined); goto error; } if (str[i] != str2[i]) break; } if (i > min && len2 < Str->length) { code = gs_note_error(gs_error_undefined); goto error; } gs_free_object(ctx->memory, str2, "pdfi_get_named_dest"); str2 = NULL; code = pdfi_array_get_type(ctx, Limits, 1, PDF_STRING, (pdf_obj **)&Str); if (code < 0) goto error; if (pdfi_type_of(Str) == PDF_NAME) { code = pdfi_string_from_name(ctx, (pdf_name *)Str, &str2, &len2); if (code < 0) return code; } else { len2 = ((pdf_string *)Str)->length; str2 = (char *)gs_alloc_bytes(ctx->memory, len2 + 1, "pdfi_get_named_dest"); if (str2 == NULL) { code = gs_note_error(gs_error_VMerror); goto error; } memcpy(str2, ((pdf_string *)Str)->data, len2); str2[len2] = 0; } pdfi_countdown(Str); Str = NULL; min = len; if (len2 < min) min = len2; for (i=0;i< min;i++) { if (str[i] > str2[i]) { code = gs_note_error(gs_error_undefined); goto error; } if (str[i] != str2[i]) break; } if (i > min && len > len2) code = gs_note_error(gs_error_undefined); error: gs_free_object(ctx->memory, str2, "pdfi_get_named_dest"); pdfi_countdown(Str); pdfi_countdown(Limits); return code; } static int pdfi_get_name_from_node(pdf_context *ctx, pdf_dict *node, char *str, pdf_obj **Name) { int i = 0, len = strlen(str), code = 0; pdf_string *StrKey = NULL; pdf_array *NamesArray = NULL; pdf_dict *Kid = NULL; bool known; code = pdfi_dict_known(ctx, node, "Names", &known); if (code < 0) goto error; if (known) { code = pdfi_dict_known(ctx, node, "Limits", &known); if (code < 0) goto error; if (!known) { /* No Limits array (a required entry), so just assume that the * string is in this node and check all the Names anyway */ pdfi_set_warning(ctx, 0, NULL, PDF_W_NO_TREE_LIMITS, "pdfi_get_name_from_node", 0); } else { code = pdfi_check_limits(ctx, node, str, len); if (code < 0) goto error; } code = pdfi_dict_get_type(ctx, node, "Names", PDF_ARRAY, (pdf_obj **)&NamesArray); if (code < 0) goto error; if (pdfi_array_size(NamesArray) & 1) pdfi_set_warning(ctx, 0, NULL, PDF_W_NAMES_ARRAY_SIZE, "pdfi_get_name_from_node", 0); for (i = 0;i < pdfi_array_size(NamesArray) / 2; i++) { code = pdfi_array_get_type(ctx, NamesArray, i * 2, PDF_STRING, (pdf_obj **)&StrKey); if (code < 0) goto error; if (StrKey->length == len && strncmp((const char *)StrKey->data, str, len) == 0) { code = pdfi_array_get(ctx, NamesArray, (i * 2) + 1, (pdf_obj **)Name); goto error; } pdfi_countdown(StrKey); StrKey = NULL; } pdfi_countdown(NamesArray); NamesArray = NULL; } /* Either no Names array (initial node) or not in array */ code = pdfi_dict_get_type(ctx, node, "Kids", PDF_ARRAY, (pdf_obj **)&NamesArray); if (code < 0) goto error; for (i = 0;i < pdfi_array_size(NamesArray); i++) { code = pdfi_array_get_type(ctx, NamesArray, i, PDF_DICT, (pdf_obj **)&Kid); if (code < 0) goto error; code = pdfi_get_name_from_node(ctx, Kid, str, Name); pdfi_countdown(Kid); Kid = NULL; if (code == 0) break; if (code < 0) { if (code == gs_error_undefined) continue; goto error; } } error: pdfi_countdown(Kid); pdfi_countdown(StrKey); pdfi_countdown(NamesArray); return code; } static int pdfi_get_named_dest(pdf_context *ctx, pdf_obj *Named, pdf_obj **Dest) { int code = 0, len = 0; pdf_dict *Names = NULL, *Dests = NULL; char *str = NULL; code = pdfi_dict_get_type(ctx, ctx->Root, "Names", PDF_DICT, (pdf_obj **)&Names); if (code < 0) goto error; code = pdfi_dict_get_type(ctx, Names, "Dests", PDF_DICT, (pdf_obj **)&Dests); if (code < 0) goto error; if (pdfi_type_of(Named) == PDF_NAME) { code = pdfi_string_from_name(ctx, (pdf_name *)Named, &str, &len); if (code < 0) return code; } else { len = ((pdf_string *)Named)->length; str = (char *)gs_alloc_bytes(ctx->memory, len + 1, "pdfi_get_named_dest"); if (str == NULL) { code = gs_note_error(gs_error_VMerror); goto error; } memcpy(str, ((pdf_string *)Named)->data, len); str[len] = 0; } code = pdfi_get_name_from_node(ctx, Dests, str, Dest); error: if (pdfi_type_of(Named) == PDF_NAME) (void)pdfi_free_string_from_name(ctx, str); else gs_free_object(ctx->memory, str, "pdfi_get_named_dest"); pdfi_countdown(Names); pdfi_countdown(Dests); return code; } /* Special handling for "A" in Link annotations and Outlines * Will delete A if handled and if A_key is provided. */ int pdfi_pdfmark_modA(pdf_context *ctx, pdf_dict *dict) { int code = 0; pdf_dict *A_dict = NULL; bool known; pdf_name *S_name = NULL; pdf_array *D_array = NULL; bool delete_A = false; bool deref_A = true; code = pdfi_dict_get_no_store_R(ctx, dict, "A", (pdf_obj **)&A_dict); if (code < 0) goto exit; if (pdfi_type_of(A_dict) != PDF_DICT) { /* Invalid AP, just delete it because I dunno what to do... * TODO: Should flag a warning here */ delete_A = true; goto exit; } /* Handle URI */ code = pdfi_dict_known(ctx, A_dict, "URI", &known); if (code < 0) goto exit; if (known) { code = pdfi_resolve_indirect_loop_detect(ctx, (pdf_obj *)NULL, (pdf_obj *)A_dict, true); goto exit; } /* Handle S = GoTo */ /* TODO: this handles <> * Not sure if there are other cases to handle? */ code = pdfi_dict_knownget_type(ctx, A_dict, "S", PDF_NAME, (pdf_obj **)&S_name); if (code <= 0) goto exit; /* We only handle GoTo for now */ if (pdfi_name_is(S_name, "GoTo")) { code = pdfi_dict_knownget(ctx, A_dict, "D", (pdf_obj **)&D_array); if (code <= 0) goto exit; if (pdfi_type_of(D_array) == PDF_STRING || pdfi_type_of(D_array) == PDF_NAME) { pdf_obj *Dest = NULL; code = pdfi_get_named_dest(ctx, (pdf_obj *)D_array, &Dest); if (code < 0) goto exit; pdfi_countdown(D_array); D_array = NULL; if (pdfi_type_of(Dest) != PDF_DICT) { pdfi_countdown(Dest); code = gs_note_error(gs_error_typecheck); goto exit; } code = pdfi_dict_knownget(ctx, (pdf_dict *)Dest, "D", (pdf_obj **)&D_array); pdfi_countdown(Dest); if (code <= 0) goto exit; } if (pdfi_type_of(D_array) != PDF_ARRAY) { code = gs_note_error(gs_error_typecheck); goto exit; } /* Process the D array to replace with /Page /View */ code = pdfi_pdfmark_add_Page_View(ctx, dict, D_array); if (code < 0) goto exit; delete_A = true; } else if (pdfi_name_is(S_name, "GoToR") || pdfi_name_is(S_name, "Launch")) { /* These point out of the document. * Flatten out the reference, but otherwise leave it alone. * gs does some wack stuff here. * * Currently this is same behavior as gs, but it is not correct behavior. * In at least some cases we could do better, for example if the doc * pointed to happens to be the same file. * Sample: fts_28_2808.pdf * Sample: ~/work/samples/tests_private/pdf/sumatra/1874_-_clicking_ToC_link_crashes.pdf */ code = pdfi_resolve_indirect_loop_detect(ctx, (pdf_obj *)dict, (pdf_obj *)A_dict, true); delete_A = false; code = 0; } else if (pdfi_name_is(S_name, "Named")) { /* We can just pass this through and it will work fine * This should be a name like "FirstPage" or "LastPage". * Note: gs implementation translates into page numbers and also has some bugs... * Sample: fts_33_3310.pdf */ delete_A = false; code = 0; } else if (pdfi_name_is(S_name, "GoToE")) { /* TODO: ?? * File: fts_33_3303.pdf */ } else if (pdfi_name_is(S_name, "Thread")) { /* TODO: For basically all of these below, I think just need to preserve * any references and streams and pass it all through. * File: fts_33_3305.pdf fts_33_3317.pdf */ deref_A = false; } else if (pdfi_name_is(S_name, "Sound")) { /* TODO: ?? * File: fts_33_3307.pdf */ deref_A = false; } else if (pdfi_name_is(S_name, "Movie")) { /* TODO: ?? * File: fts_33_3308.pdf */ deref_A = false; } else if (pdfi_name_is(S_name, "GoTo3DView")) { /* TODO: ?? * File: fts_33_3318.pdf */ } else if (pdfi_name_is(S_name, "RichMediaExecute")) { /* TODO: ?? * File: fts_33_3319.pdf */ } else if (pdfi_name_is(S_name, "Rendition")) { /* TODO: make sure to pass through accurately? * File: fts_07_0709.pdf fts_33_3316.pdf */ } else { /* TODO: flag warning? */ } exit: if (delete_A) { code = pdfi_dict_delete(ctx, dict, "A"); } else if (deref_A) { pdfi_countdown(A_dict); A_dict = NULL; } pdfi_countdown(A_dict); pdfi_countdown(S_name); pdfi_countdown(D_array); return code; } /* Begin defining an object * Send an OBJ (_objdef) command * (_objdef) (