Index: glib/poppler-document.cc =================================================================== RCS file: /cvs/poppler/poppler/glib/poppler-document.cc,v retrieving revision 1.39 diff -u -8 -p -B -r1.39 poppler-document.cc --- glib/poppler-document.cc 28 Jan 2007 15:36:09 -0000 1.39 +++ glib/poppler-document.cc 28 Jan 2007 21:06:42 -0000 @@ -1394,8 +1394,168 @@ poppler_ps_file_set_duplex (PopplerPSFil * **/ void poppler_ps_file_free (PopplerPSFile *ps_file) { g_return_if_fail (ps_file != NULL); g_object_unref (ps_file); } + +void decode_id (int id, int *pageNum, int *fieldNum) +{ + (*pageNum) = id >> 16; + (*fieldNum) = (id << 16) >> 16; +} + +void +poppler_document_set_form_field_button_state (PopplerDocument *document, unsigned id, gboolean state) +{ + int pageNum, fieldNum; + decode_id(id, &pageNum, &fieldNum); + Catalog *catalog = document->doc->getCatalog(); + FormWidget *tmp = catalog->getPage(pageNum)->getPageWidgets()->getWidget(fieldNum); + FormWidgetButton *field = NULL; + if (tmp->getType() != formButton) { + g_warning("poppler_document_set_form_field_button_state, invalid field type: %i\n", tmp->getType()); + return; + } + printf("poppler_document_set_form_field_button_state\n"); + field = static_cast(tmp); + if (field) { + field->setState((GBool)state); + } +} + +gboolean +poppler_document_get_form_field_button_state (PopplerDocument *document, unsigned id) +{ + int pageNum, fieldNum; + decode_id(id, &pageNum, &fieldNum); + Catalog *catalog = document->doc->getCatalog(); + FormWidget *tmp = catalog->getPage(pageNum)->getPageWidgets()->getWidget(fieldNum); + FormWidgetButton *field = NULL; + if (tmp->getType() != formButton) { + g_warning("poppler_document_set_form_field_button_state, invalid field type: %i\n", tmp->getType()); + return FALSE; + } + field = static_cast(tmp); + if(field) { + return (gboolean)field->getState(); + } + return FALSE; +} + +void +poppler_document_set_form_field_text_content (PopplerDocument *document, unsigned id, char *content, int length) +{ + int pageNum, fieldNum; + decode_id(id, &pageNum, &fieldNum); + + Catalog *catalog = document->doc->getCatalog(); + FormWidget *tmp = catalog->getPage(pageNum)->getPageWidgets()->getWidget(fieldNum); + FormWidgetText *field = NULL; + if (tmp->getType() != formText) { + g_warning("poppler_document_set_form_field_text_content, invalid field type: %i\n", tmp->getType()); + return ; + } + field = static_cast(tmp); + if (field) { + printf("poppler_document_set_form_field_text_content: length: %i\n", strlen(content)); + GooString *tmp = new GooString(content, length); + field->setContentCopy(tmp); + delete tmp; + } +} + +gchar * +poppler_document_get_form_field_text_content (PopplerDocument *document, unsigned id, int* length) +{ + PopplerFormField *field = poppler_document_find_form_field_by_id(document, id); + if (field && field->type == POPPLER_FORM_FIELD_TEXT) { + *length = field->text.length; + return field->text.content; + } else { + g_warning("poppler_document_get_form_field_text_content, unknown id: %i", id); + return NULL; + } +} + +FormWidget * +_get_form_widget_by_id (PopplerDocument *document, unsigned id) +{ + Catalog *catalog = document->doc->getCatalog(); + //decode id + int pageNum = id >> 16; + int fieldNum = (id << 16) >> 16; + FormWidget *field = catalog->getPage(pageNum)->getPageWidgets()->getWidget(fieldNum); + if (field != NULL) { + return field; + } else + return NULL; +} + +gchar * +poppler_document_get_form_field_choice_content (PopplerDocument *document, unsigned id, int index, int *length) +{ + FormWidget *field = _get_form_widget_by_id(document, id); + if (field && field->getType() == formChoice) { + *length = static_cast(field)->getChoice(index)->getLength(); + return static_cast(field)->getChoice(index)->getCString(); + } else { + g_warning("poppler_document_get_form_field_choice_content, unknown id: %i", id); + return NULL; + } +} + +int +poppler_document_get_form_field_choice_selected (PopplerDocument *document, unsigned id) +{ + FormWidget *field = _get_form_widget_by_id(document, id); + if (field && field->getType() == formChoice) { + return static_cast(field)->getSelected(); + } else { + g_warning("poppler_document_get_form_field_choice_content, unknown id: %i", id); + return -1; + } + +} + +int +poppler_document_get_form_field_choice_num_choices (PopplerDocument *document, unsigned id) +{ + FormWidget *field = _get_form_widget_by_id(document, id); + if (field && field->getType() == formChoice) { + return static_cast(field)->getNumChoices(); + } else { + g_warning("poppler_document_get_form_field_choice_content, unknown id: %i", id); + return -1; + } +} + +void poppler_document_set_form_field_choice_content (PopplerDocument *document, unsigned id, int index) +{ + FormWidget *field = _get_form_widget_by_id(document, id); + if (field && field->getType() == formChoice) { + static_cast(field)->setChoice(index); + } else { + g_warning("poppler_document_get_form_field_choice_content, unknown id: %i", id); + } +} + + + +PopplerFormField * +poppler_document_find_form_field_by_id (PopplerDocument *document, unsigned id) +{ + Catalog *catalog = document->doc->getCatalog(); + //decode id + unsigned pageNum; + unsigned fieldNum; + FormWidget::decodeID(id, &pageNum, &fieldNum); + FormWidget *field = catalog->getPage(pageNum)->getPageWidgets()->getWidget(fieldNum); + if (field != NULL) { + PopplerFormField *poppler_field = _form_field_new_from_widget (field); + return poppler_field; + } else + return NULL; +} + Index: glib/poppler-document.h =================================================================== RCS file: /cvs/poppler/poppler/glib/poppler-document.h,v retrieving revision 1.22 diff -u -8 -p -B -r1.22 poppler-document.h --- glib/poppler-document.h 29 Dec 2006 04:12:40 -0000 1.22 +++ glib/poppler-document.h 28 Jan 2007 21:06:42 -0000 @@ -107,16 +107,41 @@ PopplerPage *poppler_document_get_pa /* Attachments */ gboolean poppler_document_has_attachments (PopplerDocument *document); GList *poppler_document_get_attachments (PopplerDocument *document); /* Links */ PopplerDest *poppler_document_find_dest (PopplerDocument *document, const gchar *link_name); +/* Form */ +void poppler_document_set_form_field_text_content (PopplerDocument *document, + unsigned id, + char *content, + int length); +PopplerFormField *poppler_document_find_form_field_by_id (PopplerDocument *document, + unsigned id); +void poppler_document_set_form_field_button_state (PopplerDocument *document, unsigned id, gboolean state); + +gboolean poppler_document_get_form_field_button_state (PopplerDocument *document, unsigned id); + +gchar * +poppler_document_get_form_field_text_content (PopplerDocument *document, unsigned id, int *length); +int +poppler_document_get_form_field_choice_num_choices (PopplerDocument *document, unsigned id); +gchar * +poppler_document_get_form_field_choice_content (PopplerDocument *document, unsigned id, int index, int *length); +void +poppler_document_set_form_field_choice_content (PopplerDocument *document, unsigned id, int index); +int +poppler_document_get_form_field_choice_selected (PopplerDocument *document, unsigned id); + + + + /* Interface for getting the Index of a poppler_document */ #define POPPLER_TYPE_INDEX_ITER (poppler_index_iter_get_type ()) GType poppler_index_iter_get_type (void) G_GNUC_CONST; PopplerIndexIter *poppler_index_iter_new (PopplerDocument *document); PopplerIndexIter *poppler_index_iter_copy (PopplerIndexIter *iter); void poppler_index_iter_free (PopplerIndexIter *iter); PopplerIndexIter *poppler_index_iter_get_child (PopplerIndexIter *parent); Index: glib/poppler-page.cc =================================================================== RCS file: /cvs/poppler/poppler/glib/poppler-page.cc,v retrieving revision 1.53 diff -u -8 -p -B -r1.53 poppler-page.cc --- glib/poppler-page.cc 28 Jan 2007 15:55:11 -0000 1.53 +++ glib/poppler-page.cc 28 Jan 2007 21:06:46 -0000 @@ -1033,8 +1033,128 @@ poppler_link_mapping_copy (PopplerLinkMa void poppler_link_mapping_free (PopplerLinkMapping *mapping) { if (mapping) poppler_action_free (mapping->action); g_free (mapping); } + + +/* Form Type */ +GType +poppler_form_field_get_type (void) +{ + static GType our_type = 0; + if (our_type == 0) + our_type = g_boxed_type_register_static("PopplerFormField", + (GBoxedCopyFunc) poppler_form_field_copy, + (GBoxedFreeFunc) poppler_form_field_free); + return our_type; +} + +PopplerFormField* +poppler_form_field_new (void) +{ + return (PopplerFormField *) g_new0 (PopplerFormField, 1); +} + +PopplerFormField* +poppler_form_field_copy (PopplerFormField* field) +{ + PopplerFormField* new_field; + new_field = poppler_form_field_new(); + new_field = field; + return new_field; +} + +void +poppler_form_field_free (PopplerFormField* field) +{ + g_free (field); +} + +PopplerFormField * +_form_field_new_from_widget (FormWidget* field) +{ + PopplerFormField *poppler_field = g_new(PopplerFormField, 1); + field->getRect (&(poppler_field->area.x1), &(poppler_field->area.y1), + &(poppler_field->area.x2), &(poppler_field->area.y2)); + + poppler_field->type = (PopplerFormFieldType)field->getType(); + poppler_field->id = field->getID(); + if (poppler_field->type == POPPLER_FORM_FIELD_TEXT) { + FormWidgetText* wid = static_cast(field); + GooString *tmp = wid->getContentCopy(); + poppler_field->text.content = (tmp)?tmp->getCString():NULL; + poppler_field->text.length = (tmp)?tmp->getLength():0; + poppler_field->text.multiline = wid->isMultiline(); + poppler_field->text.password = wid->isPassword(); + poppler_field->text.fileselect = wid->isFileSelect(); + poppler_field->text.do_not_spell_check = wid->noSpellCheck(); + poppler_field->text.do_not_scroll = wid->noScroll(); + poppler_field->text.rich_text = wid->isRichText(); + poppler_field->text.font_size = wid->getFontSize(); + } else if (poppler_field->type == POPPLER_FORM_FIELD_BUTTON) { + poppler_field->button.state = (gboolean)static_cast(field)->getState(); + } else if (poppler_field->type == POPPLER_FORM_FIELD_CHOICE) { + FormWidgetChoice* wid = static_cast(field); + poppler_field->choice.combo = wid->isCombo(); + poppler_field->choice.edit = wid->hasEdit(); + poppler_field->choice.multi_select = wid->isMultiSelect(); + poppler_field->choice.do_not_spell_check = wid->noSpellCheck(); + } + return poppler_field; +} + +/** + * poppler_page_get_form_fields + **/ + +GList* +poppler_page_get_form_fields (PopplerPage *page) +{ + GList *field_list = NULL; + FormPageWidgets *form; + gint i; + Object obj; + + g_return_val_if_fail (POPPLER_IS_PAGE (page), NULL); + + form = page->page->getPageWidgets(); + + obj.free (); + if(form == NULL) + return NULL; + + for(i = 0; i < form->getNumWidgets(); i++) { + PopplerFormField *poppler_field; + FormWidget *field; + field = form->getWidget(i); + poppler_field = _form_field_new_from_widget (field); + field_list = g_list_prepend(field_list,poppler_field); + } + return field_list; + +} + +void +poppler_page_free_form_fields (GList *list) +{ + if (list == NULL) + return; + + g_list_foreach (list, (GFunc) (poppler_form_field_free), NULL); + g_list_free (list); + +} + +void +poppler_page_get_crop_box (PopplerPage *page, PopplerRectangle *rect) +{ + PDFRectangle* cropBox = page->page->getCropBox(); + rect->x1 = cropBox->x1; + rect->x2 = cropBox->x2; + rect->y1 = cropBox->y1; + rect->y2 = cropBox->y2; +} + Index: glib/poppler-page.h =================================================================== RCS file: /cvs/poppler/poppler/glib/poppler-page.h,v retrieving revision 1.22 diff -u -8 -p -B -r1.22 poppler-page.h --- glib/poppler-page.h 26 Dec 2006 19:56:29 -0000 1.22 +++ glib/poppler-page.h 28 Jan 2007 21:06:46 -0000 @@ -76,16 +76,21 @@ GdkRegion * poppler_page_get_sel void poppler_page_render_selection (PopplerPage *page, gdouble scale, int rotation, GdkPixbuf *pixbuf, PopplerRectangle *selection, PopplerRectangle *old_selection, GdkColor *glyph_color, GdkColor *background_color); +GList *poppler_page_get_form_fields (PopplerPage *page); +void poppler_page_free_form_fields (GList *list); + +void poppler_page_get_crop_box (PopplerPage *page, + PopplerRectangle *rect); /* A rectangle on a page, with coordinates in PDF points. */ #define POPPLER_TYPE_RECTANGLE (poppler_rectangle_get_type ()) struct _PopplerRectangle { gdouble x1; gdouble y1; @@ -108,11 +113,61 @@ struct _PopplerLinkMapping PopplerAction *action; }; GType poppler_link_mapping_get_type (void) G_GNUC_CONST; PopplerLinkMapping *poppler_link_mapping_new (void); PopplerLinkMapping *poppler_link_mapping_copy (PopplerLinkMapping *mapping); void poppler_link_mapping_free (PopplerLinkMapping *mapping); +/* FormField */ +#define POPPLER_TYPE_FORM_FIELD (poppler_form_field_get_type ()) +struct _PopplerTextField +{ + //flags + char multiline:1; + char password:1; + char fileselect:1; + char do_not_spell_check:1; + char do_not_scroll:1; + char comb:1; + char rich_text:1; + //content + gchar *content; + int length; + double font_size; +}; + +struct _PopplerButtonField +{ + //content + gboolean state; +}; + +struct _PopplerChoiceField +{ + char combo:1; + char edit:1; + char multi_select:1; + char do_not_spell_check:1; + char commit_on_sel_change:1; +}; + +struct _PopplerFormField +{ + PopplerRectangle area; + PopplerFormFieldType type; + int id; + union { + PopplerTextField text; + PopplerButtonField button; + PopplerChoiceField choice; + }; +}; + +GType poppler_form_field_get_type (void) G_GNUC_CONST; +PopplerFormField *poppler_form_field_new (void); +PopplerFormField *poppler_form_field_copy (PopplerFormField *field); +void poppler_form_field_free (PopplerFormField *field); + G_END_DECLS #endif /* __POPPLER_PAGE_H__ */ Index: glib/poppler-private.h =================================================================== RCS file: /cvs/poppler/poppler/glib/poppler-private.h,v retrieving revision 1.16 diff -u -8 -p -B -r1.16 poppler-private.h --- glib/poppler-private.h 19 May 2006 20:54:13 -0000 1.16 +++ glib/poppler-private.h 28 Jan 2007 21:06:46 -0000 @@ -1,15 +1,16 @@ #ifndef __POPPLER_PRIVATE_H__ #define __POPPLER_PRIVATE_H__ #include #include #include #include +#include #include #include #include #include #include "poppler-attachment.h" #if defined (HAVE_CAIRO) @@ -55,16 +56,19 @@ struct _PopplerPage GObject parent_instance; PopplerDocument *document; Page *page; int index; TextOutputDev *text_dev; Gfx *gfx; }; +PopplerFormField *_form_field_new_from_widget (FormWidget* field); + + PopplerPage *_poppler_page_new (PopplerDocument *document, Page *page, int index); PopplerAction *_poppler_action_new (PopplerDocument *document, LinkAction *link, const gchar *title); PopplerDest *_poppler_dest_new_goto (PopplerDocument *document, LinkDest *link_dest); Index: glib/poppler.h =================================================================== RCS file: /cvs/poppler/poppler/glib/poppler.h,v retrieving revision 1.12 diff -u -8 -p -B -r1.12 poppler.h --- glib/poppler.h 16 Apr 2006 22:59:44 -0000 1.12 +++ glib/poppler.h 28 Jan 2007 21:06:46 -0000 @@ -37,27 +37,44 @@ typedef enum typedef enum { POPPLER_ORIENTATION_PORTRAIT, POPPLER_ORIENTATION_LANDSCAPE, POPPLER_ORIENTATION_UPSIDEDOWN, POPPLER_ORIENTATION_SEASCAPE } PopplerOrientation; +/* MUST be the same than poppler/Form.h fieldType */ +typedef enum +{ + POPPLER_FORM_FIELD_BUTTON, + POPPLER_FORM_FIELD_TEXT, + POPPLER_FORM_FIELD_CHOICE, + POPPLER_FORM_FIELD_SIGNATURE, + //kid type + POPPLER_FORM_FIELD_BUTTON_CHECK, + POPPLER_FORM_FIELD_BUTTON_PUSH, + POPPLER_FORM_FIELD_BUTTON_RADIO +} PopplerFormFieldType; + + typedef struct _PopplerDocument PopplerDocument; typedef struct _PopplerIndexIter PopplerIndexIter; typedef struct _PopplerFontsIter PopplerFontsIter; typedef struct _PopplerRectangle PopplerRectangle; typedef struct _PopplerLinkMapping PopplerLinkMapping; +typedef struct _PopplerFormField PopplerFormField; typedef struct _PopplerPage PopplerPage; typedef struct _PopplerFontInfo PopplerFontInfo; typedef struct _PopplerPSFile PopplerPSFile; typedef union _PopplerAction PopplerAction; typedef struct _PopplerDest PopplerDest; - +typedef struct _PopplerTextField PopplerTextField; +typedef struct _PopplerButtonField PopplerButtonField; +typedef struct _PopplerChoiceField PopplerChoiceField; typedef enum { POPPLER_BACKEND_UNKNOWN, POPPLER_BACKEND_SPLASH, POPPLER_BACKEND_CAIRO } PopplerBackend; Index: poppler/Annot.cc =================================================================== RCS file: /cvs/poppler/poppler/poppler/Annot.cc,v retrieving revision 1.6 diff -u -8 -p -B -r1.6 Annot.cc --- poppler/Annot.cc 27 Dec 2006 23:15:06 -0000 1.6 +++ poppler/Annot.cc 28 Jan 2007 21:06:46 -0000 @@ -15,29 +15,39 @@ #include #include "goo/gmem.h" #include "Object.h" #include "Catalog.h" #include "Gfx.h" #include "Lexer.h" #include "UGooString.h" #include "Annot.h" +#include "GfxFont.h" +#include "CharCodeToUnicode.h" +#include "Form.h" //------------------------------------------------------------------------ // Annot //------------------------------------------------------------------------ -Annot::Annot(XRef *xrefA, Dict *acroForm, Dict *dict) { +Annot::Annot(XRef *xrefA, Dict *acroForm, Dict *dict, Ref* aref, Catalog* catalog) { Object apObj, asObj, obj1, obj2; - GBool regen, isTextField; double t; ok = gTrue; xref = xrefA; appearBuf = NULL; + fontSize = 0; + widget = NULL; + + if (aref) { + hasRef = true; + ref = *aref; + } else + hasRef = false; if (dict->lookup("Rect", &obj1)->isArray() && obj1.arrayGetLength() == 4) { readArrayNum(&obj1, 0, &xMin); readArrayNum(&obj1, 1, &yMin); readArrayNum(&obj1, 2, &xMax); readArrayNum(&obj1, 3, &yMax); if (ok) { @@ -63,55 +73,75 @@ Annot::Annot(XRef *xrefA, Dict *acroForm regen = gFalse; if (acroForm) { acroForm->lookup("NeedAppearances", &obj1); if (obj1.isBool() && obj1.getBool()) { regen = gTrue; } obj1.free(); } + regen = gTrue; - // check for a text-type field - isTextField = dict->lookup("FT", &obj1)->isName("Tx"); - obj1.free(); + /*TODO: appearance generation isn't complete : + non-comprehensive list of missings things by Leonard Rosenthol : + * Doesn't support walking up the parents of the field for /DA information + * Doesn't support field attributes + border style (width, color, etc.) + background color + flags (password, comb, etc.) + etc. + * Only works for text fields, basic support for combos + */ + + //try to find the corresponding FormWidget + if (hasRef) { + widget = catalog->getForm()->findWidgetByRef(ref); + } -#if 0 //~ appearance stream generation is not finished yet + isMultiline = isListbox = false; + if (widget) { + if (widget->getType() == formText) { + isTextField = true; + isMultiline = static_cast(widget)->isMultiline(); + } else if (widget->getType() == formChoice) { + isTextField = true; + isListbox = static_cast(widget)->isListBox(); + } + } + if (regen && isTextField) { generateAppearance(acroForm, dict); } else { -#endif if (dict->lookup("AP", &apObj)->isDict()) { if (dict->lookup("AS", &asObj)->isName()) { - if (apObj.dictLookup("N", &obj1)->isDict()) { - if (obj1.dictLookupNF(asObj.getName(), &obj2)->isRef()) { - obj2.copy(&appearance); - ok = gTrue; - } else { - obj2.free(); - if (obj1.dictLookupNF("Off", &obj2)->isRef()) { - obj2.copy(&appearance); - ok = gTrue; - } - } - obj2.free(); - } - obj1.free(); + if (apObj.dictLookup("N", &obj1)->isDict()) { + if (obj1.dictLookupNF(asObj.getName(), &obj2)->isRef()) { + obj2.copy(&appearance); + ok = gTrue; + } else { + obj2.free(); + if (obj1.dictLookupNF("Off", &obj2)->isRef()) { + obj2.copy(&appearance); + ok = gTrue; + } + } + obj2.free(); + } + obj1.free(); } else { - if (apObj.dictLookupNF("N", &obj1)->isRef()) { - obj1.copy(&appearance); - ok = gTrue; - } - obj1.free(); + if (apObj.dictLookupNF("N", &obj1)->isRef()) { + obj1.copy(&appearance); + ok = gTrue; + } + obj1.free(); } asObj.free(); } apObj.free(); -#if 0 //~ appearance stream generation is not finished yet } -#endif } void Annot::readArrayNum(Object *pdfArray, int key, double *value) { Object valueObject; pdfArray->arrayGet(key, &valueObject); if (valueObject.isNum()) { *value = valueObject.getNum(); @@ -124,160 +154,332 @@ void Annot::readArrayNum(Object *pdfArra Annot::~Annot() { appearance.free(); if (appearBuf) { delete appearBuf; } } +void Annot::writeTextString (GooString* vStr, CharCodeToUnicode* ccToUnicode, GooString* appearBuf, GfxFont* font) +{ + int i0,i1; //i0 = line begin index, i1 = line end index + int charSize; + CharCode c; + char buf[256]; + double currentLineWidth= 0.0; //width filled by displayed chars (used for multilines) + + if (vStr->hasUnicodeMarker()) { + //FIXME: we start at 3 because we skip the second character of the unicode string + // but we should take it into account and pass it to ccToUnicode + i0 = 3; + charSize = 2; + } else { + i0 = 0; + charSize = 1; + } + while (i0 < vStr->getLength()) { + //find next end of line + for (i1 = i0; i1 < vStr->getLength(); i1++) { + if (vStr->getChar(i1) == '\n' || vStr->getChar(i1) == '\r') + break; + } + appearBuf->append('('); + + for(; i0 < i1; i0 += charSize) { + c = vStr->getChar(i0); + if (ccToUnicode) { + char ctmp[2]; + ctmp[0] = vStr->getChar(i0); + ctmp[1] = vStr->getChar(i0-1); + ccToUnicode->mapToCharCode((Unicode*)ctmp, &c, 2); + appearBuf->append(c); + } else { + c &= 0xff; + if (c == '(' || c == ')' || c == '\\') { + appearBuf->append('\\'); + appearBuf->append(c); + } else if (c < 0x20 || c >= 0x80) { + sprintf(buf, "\\%03o", c); + appearBuf->append(buf); + } else { + appearBuf->append(c); + } + } + if (font) { + if (font->isCIDFont()) { + currentLineWidth += fontSize*static_cast(font)->getWidth((char*)&c, 1); + } else { //Gfx8Bit + currentLineWidth += fontSize*static_cast(font)->getWidth((char)c); + } + + if (isMultiline && (currentLineWidth >= xMax - xMin)) { + currentLineWidth = 0; + break; + } + } else { + + } + } + appearBuf->append(") Tj\n"); + appearBuf->append("T*\n"); + i0 = i0 + charSize; + } +} + void Annot::generateAppearance(Dict *acroForm, Dict *dict) { MemStream *appearStream; - Object daObj, vObj, drObj, appearDict, obj1, obj2; + Object daObj, vObj, drObj, appearDict, obj1, obj2, resObj; GooString *daStr, *daStr1, *vStr, *s; + GooString *fontName=NULL; char buf[256]; - double fontSize; int c; int i0, i1; + GfxFont *font = NULL; + //~ DA can be inherited if (dict->lookup("DA", &daObj)->isString()) { daStr = daObj.getString(); // look for a font size //~ may want to parse the DS entry in place of this (if it exists) daStr1 = NULL; fontSize = 10; + //printf("Annot::generateAppearance: %s\n", daStr->getCString()); for (i1 = daStr->getLength() - 2; i1 >= 0; --i1) { if (daStr->getChar(i1) == 'T' && daStr->getChar(i1+1) == 'f') { - for (--i1; i1 >= 0 && Lexer::isSpace(daStr->getChar(i1)); --i1) ; - for (i0 = i1; i0 >= 0 && !Lexer::isSpace(daStr->getChar(i0)); --i0) ; - if (i0 >= 0) { - ++i0; - ++i1; - s = new GooString(daStr, i0, i1 - i0); - fontSize = atof(s->getCString()); - delete s; - - // autosize the font - if (fontSize == 0) { - fontSize = 0.67 * (yMax - yMin); - daStr1 = new GooString(daStr, 0, i0); - sprintf(buf, "%.2f", fontSize); - daStr1->append(buf); - daStr1->append(daStr->getCString() + i1, - daStr->getLength() - i1); - } - } - break; + for (--i1; i1 >= 0 && Lexer::isSpace(daStr->getChar(i1)); --i1) ; + for (i0 = i1; i0 >= 0 && !Lexer::isSpace(daStr->getChar(i0)); --i0) ; + if (i0 >= 0) { + ++i0; + ++i1; + s = new GooString(daStr, i0, i1 - i0); + fontSize = atof(s->getCString()); + delete s; + + // autosize the font + if (fontSize == 0) { + fontSize = 0.67 * (yMax - yMin); + daStr1 = new GooString(daStr, 0, i0); + sprintf(buf, "%.2f", fontSize); + daStr1->append(buf); + daStr1->append(daStr->getCString() + i1, + daStr->getLength() - i1); + } + } + + //find the font name + i1=i0; + for (--i1; i1 >= 0 && Lexer::isSpace(daStr->getChar(i1)); --i1) ; + for (i0 = i1; i0 >= 0 && !Lexer::isSpace(daStr->getChar(i0)); --i0) ; + if (i0<0) i0=0; + if (i0 != i1) { + ++i0; + ++i1; + fontName = new GooString(daStr, i0, i1 - i0); + } + + break; + } + } + + //init appearance dictionnary + appearDict.initDict(xref); + + // find the resource dictionary + dict->lookup("DR", &drObj); + if (!drObj.isDict()) { + dict->lookup("Parent", &obj1); + while (obj1.isDict()) { + drObj.free(); + obj1.dictLookup("DR", &drObj); + if (drObj.isDict()) { + break; + } + obj1.dictLookup("Parent", &obj2); + obj1.free(); + obj1 = obj2; + } + obj1.free(); + if (!drObj.isDict()) { + if (acroForm) { + drObj.free(); + acroForm->lookup("DR", &drObj); + } + } + } + CharCodeToUnicode* ccToUnicode = NULL; + #if 1 + if (drObj.isDict()) { + //Lookup for the font name in the font entry of the ressource dictionnary + //also lookup in BaseFont/Name of each DR entry + //without that second lookup, forms-scribus.pdf doesn't find font + if (fontName && drObj.dictLookup("Font", &obj1)->isDict()) { + Object obj3, obj4; + //printf("\n\nsearching for %s\n\n", fontName->getCString()); + //-- + bool found = false; + if (obj1.dictLookup(*fontName, &obj3)->isDict() && obj3.dictLookup("Type", &obj4)->isName("Font")) { + found = true; + } + if (!found) { //font not found in DR, try to lookup in each entry BaseFont/Name + for(int i=0; iisDict()) { + if (obj3.dictLookup("Type", &obj4)->isName("Font")) { + obj4.free(); + if (obj3.dictLookup("Name", &obj4)->isName()) { + if (fontName->cmp(obj4.getName()) == 0) { + //printf("Found Name\n"); + found = true; + break; + } + obj4.free(); + } + if (obj3.dictLookup("BaseFont", &obj4)->isName()) { + if (fontName->cmp(obj4.getName()) == 0) { + //printf("Found BaseFont\n"); + found = true; + break; + } + obj4.free(); + } + } + obj3.free(); + } + } + } + if (found) { + //-- + //printf("Annot::generateAppearance: looking for font name in ressources: %s\n", fontName->getCString()); +// if (obj1.dictLookup(*fontName, &obj3)->isDict() && obj3.dictLookup("Type", &obj4)->isName("Font")) { + obj4.free(); + obj1.dictLookupNF(*fontName, &obj4); + Ref r; + if (obj4.isRef()) r = obj4.getRef(); + else r.gen = r.num = 0; + font = GfxFont::makeFont(xref, "temp", r, obj3.getDict()); + ccToUnicode = font->getToUnicode(); + //printf("Annot::generateAppearance: found font: type : %i\n", font->getType()); + } + obj3.free(); } + obj1.free(); + appearDict.dictAdd("Resources", drObj.copy(&resObj)); } + drObj.free(); + #endif // build the appearance stream contents appearBuf = new GooString(); + //FIXME: BEGIN -- UGLY Border test + //appearBuf->append("0 0 m\n"); + //TODO: parse BS dict + bool drawBorder = false; + if (dict->lookup("BS", &obj1)->isDict()) { + Object obj3,obj4; + Dict *bsDict = obj1.getDict(); + if (bsDict->lookup("Type", &obj3)->isName("Border")) { + //width + if (bsDict->lookup("W", &obj4)->isInt()) { + sprintf(buf, "%i w\n", obj4.getInt()); + appearBuf->append(buf); + drawBorder = true; + } + obj4.free(); + } + obj3.free(); + } + obj1.free(); + + if (drawBorder) { + //force stroke color to red + //appearBuf->append("1.0 0.0 0.0 RG\n"); + sprintf(buf, "0 0 %f %f re\n", xMax-xMin, yMax-yMin); + appearBuf->append(buf); + appearBuf->append("S\n"); + } + // END UGLY BORDER TEST + appearBuf->append("/Tx BMC\n"); appearBuf->append("q BT\n"); appearBuf->append(daStr1 ? daStr1 : daStr)->append("\n"); - if (dict->lookup("V", &vObj)->isString()) { + if (isListbox) { + FormWidgetChoice* choice = static_cast(widget); + if (dict->lookup("V", &vObj)->isString()) { + vStr = vObj.getString(); + } + + sprintf(buf, "1 0 0 1 %.2f %.2f Tm\n", 2.0, yMax - yMin - fontSize); + appearBuf->append(buf); + sprintf(buf, "%g TL\n", fontSize); + appearBuf->append(buf); + + for(int i=0; igetNumChoices(); i++) { + if (vStr->cmp(choice->getChoice(i)) == 0) { //currently selected choice + //~ The color of the highlighting rect should perhaps depend on the color of the font. + //highlight with a black background rect + appearBuf->append("q\n"); + sprintf(buf, "0 %f %f %f re\n", yMax-yMin-(i+1)*fontSize, xMax-xMin, fontSize); + appearBuf->append(buf); + appearBuf->append("f\n"); + appearBuf->append("Q\n"); + //draw the text in white + appearBuf->append("1.0 1.0 1.0 rg\n"); + writeTextString(choice->getChoice(i), ccToUnicode, appearBuf, font); + appearBuf->append("0.0 0.0 0.0 rg\n"); + } else + writeTextString(choice->getChoice(i), ccToUnicode, appearBuf, font); + } + vObj.free(); + } + else if (dict->lookup("V", &vObj)->isString()) { //~ handle quadding -- this requires finding the font and using //~ the encoding and char widths sprintf(buf, "1 0 0 1 %.2f %.2f Tm\n", 2.0, yMax - yMin - fontSize); appearBuf->append(buf); sprintf(buf, "%g TL\n", fontSize); appearBuf->append(buf); vStr = vObj.getString(); - i0 = 0; - while (i0 < vStr->getLength()) { - for (i1 = i0; - i1 < vStr->getLength() && - vStr->getChar(i1) != '\n' && vStr->getChar(i1) != '\r'; - ++i1) ; - if (i0 > 0) { - appearBuf->append("T*\n"); - } - appearBuf->append('('); - for (; i0 < i1; ++i0) { - c = vStr->getChar(i0); - if (c == '(' || c == ')' || c == '\\') { - appearBuf->append('\\'); - appearBuf->append(c); - } else if (c < 0x20 || c >= 0x80) { - sprintf(buf, "\\%03o", c); - appearBuf->append(buf); - } else { - appearBuf->append(c); - } - } - appearBuf->append(") Tj\n"); - if (i1 + 1 < vStr->getLength() && - vStr->getChar(i1) == '\r' && vStr->getChar(i1 + 1) == '\n') { - i0 = i1 + 2; - } else { - i0 = i1 + 1; - } - } + + writeTextString(vStr, ccToUnicode, appearBuf, font); + vObj.free(); } - vObj.free(); + appearBuf->append("ET Q\n"); appearBuf->append("EMC\n"); // build the appearance stream dictionary - appearDict.initDict(xref); appearDict.dictAdd("Length", obj1.initInt(appearBuf->getLength())); appearDict.dictAdd("Subtype", obj1.initName("Form")); obj1.initArray(xref); obj1.arrayAdd(obj2.initReal(0)); obj1.arrayAdd(obj2.initReal(0)); obj1.arrayAdd(obj2.initReal(xMax - xMin)); obj1.arrayAdd(obj2.initReal(yMax - yMin)); appearDict.dictAdd("BBox", &obj1); - // find the resource dictionary - dict->lookup("DR", &drObj); - if (!drObj.isDict()) { - dict->lookup("Parent", &obj1); - while (obj1.isDict()) { - drObj.free(); - obj1.dictLookup("DR", &drObj); - if (drObj.isDict()) { - break; - } - obj1.dictLookup("Parent", &obj2); - obj1.free(); - obj1 = obj2; - } - obj1.free(); - if (!drObj.isDict()) { - if (acroForm) { - drObj.free(); - acroForm->lookup("DR", &drObj); - } - } - } - if (drObj.isDict()) { - appearDict.dictAdd("Resources", drObj.copy(&obj1)); - } - drObj.free(); - // build the appearance stream appearStream = new MemStream(appearBuf->getCString(), 0, - appearBuf->getLength(), &appearDict); + appearBuf->getLength(), &appearDict); appearance.initStream(appearStream); ok = gTrue; if (daStr1) { delete daStr1; } } daObj.free(); + + if (fontName) delete fontName; + if (font) delete font; } void Annot::draw(Gfx *gfx) { Object obj; - if (appearance.fetch(xref, &obj)->isStream()) { gfx->doAnnot(&obj, xMin, yMin, xMax, yMax); } obj.free(); } //------------------------------------------------------------------------ // Annots @@ -290,31 +492,42 @@ Annots::Annots(XRef *xref, Catalog *cata int size; int i; annots = NULL; size = 0; nAnnots = 0; acroForm = catalog->getAcroForm()->isDict() ? - catalog->getAcroForm()->getDict() : NULL; + catalog->getAcroForm()->getDict() : NULL; if (annotsObj->isArray()) { for (i = 0; i < annotsObj->arrayGetLength(); ++i) { + //get the Ref to this annot and pass it to Annot constructor + //this way, it'll be possible for the annot to retrieve the corresponding + //form widget + Object obj2; + Ref* pref; + if (annotsObj->arrayGetNF(i, &obj2)->isRef()) + pref = &obj2.getRef(); + else + pref = NULL; + if (annotsObj->arrayGet(i, &obj1)->isDict()) { - annot = new Annot(xref, acroForm, obj1.getDict()); - if (annot->isOk()) { - if (nAnnots >= size) { - size += 16; - annots = (Annot **)greallocn(annots, size, sizeof(Annot *)); - } - annots[nAnnots++] = annot; - } else { - delete annot; - } + annot = new Annot(xref, acroForm, obj1.getDict(), pref, catalog); + if (annot->isOk()) { + if (nAnnots >= size) { + size += 16; + annots = (Annot **)greallocn(annots, size, sizeof(Annot *)); + } + annots[nAnnots++] = annot; + } else { + delete annot; + } } + obj2.free(); obj1.free(); } } } Annots::~Annots() { int i; Index: poppler/Annot.h =================================================================== RCS file: /cvs/poppler/poppler/poppler/Annot.h,v retrieving revision 1.3 diff -u -8 -p -B -r1.3 Annot.h --- poppler/Annot.h 27 Dec 2006 23:15:06 -0000 1.3 +++ poppler/Annot.h 28 Jan 2007 21:06:46 -0000 @@ -11,45 +11,61 @@ #ifdef USE_GCC_PRAGMAS #pragma interface #endif class XRef; class Gfx; class Catalog; +class CharCodeToUnicode; +class GfxFont; +class FormWidget; //------------------------------------------------------------------------ // Annot //------------------------------------------------------------------------ class Annot { public: - Annot(XRef *xrefA, Dict *acroForm, Dict *dict); + Annot(XRef *xrefA, Dict *acroForm, Dict *dict, Ref* aref, Catalog* catalog); ~Annot(); GBool isOk() { return ok; } void draw(Gfx *gfx); // Get appearance object. Object *getAppearance(Object *obj) { return appearance.fetch(xref, obj); } + GBool textField() { return isTextField; } -private: + double getXMin() { return xMin; } + double getYMin() { return yMin; } + + double getFontSize() { return fontSize; } +private: + void writeTextString (GooString* vStr, CharCodeToUnicode* ccToUnicode, GooString* appearBuf, GfxFont* font); void generateAppearance(Dict *acroForm, Dict *dict); void readArrayNum(Object *pdfArray, int key, double *value); XRef *xref; // the xref table for this PDF file Object appearance; // a reference to the Form XObject stream // for the normal appearance GooString *appearBuf; double xMin, yMin, // annotation rectangle xMax, yMax; + double fontSize; GBool ok; + GBool regen, isTextField; + GBool isMultiline, isListbox; + + bool hasRef; + Ref ref; + FormWidget* widget; }; //------------------------------------------------------------------------ // Annots //------------------------------------------------------------------------ class Annots { public: Index: poppler/Catalog.cc =================================================================== RCS file: /cvs/poppler/poppler/poppler/Catalog.cc,v retrieving revision 1.18 diff -u -8 -p -B -r1.18 Catalog.cc --- poppler/Catalog.cc 11 Jan 2007 22:12:11 -0000 1.18 +++ poppler/Catalog.cc 28 Jan 2007 21:06:46 -0000 @@ -20,16 +20,17 @@ #include "Array.h" #include "Dict.h" #include "Page.h" #include "Error.h" #include "Link.h" #include "PageLabelInfo.h" #include "UGooString.h" #include "Catalog.h" +#include "Form.h" // This define is used to limit the depth of recursive readPageTree calls // This is needed because the page tree nodes can reference their parents // leaving us in an infinite loop // Most sane pdf documents don't have a call depth higher than 10 #define MAX_CALL_DEPTH 1000 //------------------------------------------------------------------------ @@ -44,22 +45,33 @@ Catalog::Catalog(XRef *xrefA) { ok = gTrue; xref = xrefA; pages = NULL; pageRefs = NULL; numPages = pagesSize = 0; baseURI = NULL; pageLabelInfo = NULL; + form = NULL; xref->getCatalog(&catDict); if (!catDict.isDict()) { error(-1, "Catalog object is wrong type (%s)", catDict.getTypeName()); goto err1; } + // get the AcroForm dictionary + catDict.dictLookup("AcroForm", &acroForm); + + // load Forms + printf("--------- Loading FORM for Document -------\n"); + if (acroForm.isDict()) { + form = new Form(xref,&acroForm); + } + printf("--------- done loading FORM -------\n"); + // read page tree catDict.dictLookup("Pages", &pagesDict); // This should really be isDict("Pages"), but I've seen at least one // PDF file where the /Type entry is missing. if (!pagesDict.isDict()) { error(-1, "Top-level pages object is wrong type (%s)", pagesDict.getTypeName()); @@ -153,18 +165,18 @@ Catalog::Catalog(XRef *xrefA) { catDict.dictLookup("Metadata", &metadata); // get the structure tree root catDict.dictLookup("StructTreeRoot", &structTreeRoot); // get the outline dictionary catDict.dictLookup("Outlines", &outline); - // get the AcroForm dictionary - catDict.dictLookup("AcroForm", &acroForm); + // perform form-related loading after all widgets have been loaded + form->postWidgetsLoad(); catDict.free(); return; err3: obj.free(); err2: pagesDict.free(); @@ -237,17 +249,17 @@ int Catalog::readPageTree(Dict *pagesDic error(-1, "Kids object (page %d) is wrong type (%s)", start+1, kids.getTypeName()); goto err1; } for (i = 0; i < kids.arrayGetLength(); ++i) { kids.arrayGet(i, &kid); if (kid.isDict("Page")) { attrs2 = new PageAttrs(attrs1, kid.getDict()); - page = new Page(xref, start+1, kid.getDict(), attrs2); + page = new Page(xref, start+1, kid.getDict(), attrs2, form); if (!page->isOk()) { ++start; goto err3; } if (start >= pagesSize) { pagesSize += 32; pages = (Page **)greallocn(pages, pagesSize, sizeof(Page *)); pageRefs = (Ref *)greallocn(pageRefs, pagesSize, sizeof(Ref)); Index: poppler/Catalog.h =================================================================== RCS file: /cvs/poppler/poppler/poppler/Catalog.h,v retrieving revision 1.10 diff -u -8 -p -B -r1.10 Catalog.h --- poppler/Catalog.h 11 Jan 2007 22:12:11 -0000 1.10 +++ poppler/Catalog.h 28 Jan 2007 21:06:46 -0000 @@ -16,16 +16,17 @@ class XRef; class Object; class Page; class PageAttrs; struct Ref; class LinkDest; class UGooString; class PageLabelInfo; +class Form; //------------------------------------------------------------------------ // NameTree //------------------------------------------------------------------------ class NameTree { public: NameTree(); @@ -146,16 +147,18 @@ public: // Convert between page indices and page labels. GBool labelToIndex(GooString *label, int *index); GBool indexToLabel(int index, GooString *label); Object *getOutline() { return &outline; } Object *getAcroForm() { return &acroForm; } + Form* getForm() { return form; } + enum PageMode { pageModeNone, pageModeOutlines, pageModeThumbs, pageModeFullScreen, pageModeOC, pageModeAttach }; @@ -173,16 +176,17 @@ public: PageMode getPageMode() { return pageMode; } PageLayout getPageLayout() { return pageLayout; } private: XRef *xref; // the xref table for this PDF file Page **pages; // array of pages Ref *pageRefs; // object ID for each page + Form *form; int numPages; // number of pages int pagesSize; // size of pages array Object dests; // named destination dictionary NameTree destNameTree; // named destination name-tree NameTree embeddedFileNameTree; // embedded file name-tree GooString *baseURI; // base URI for URI-type links Object metadata; // metadata stream Object structTreeRoot; // structure tree root dictionary Index: poppler/CharCodeToUnicode.cc =================================================================== RCS file: /cvs/poppler/poppler/poppler/CharCodeToUnicode.cc,v retrieving revision 1.4 diff -u -8 -p -B -r1.4 CharCodeToUnicode.cc --- poppler/CharCodeToUnicode.cc 18 Jan 2006 18:54:12 -0000 1.4 +++ poppler/CharCodeToUnicode.cc 28 Jan 2007 21:06:47 -0000 @@ -499,16 +499,54 @@ int CharCodeToUnicode::mapToUnicode(Char u[j] = sMap[i].u[j]; } return j; } } return 0; } +int CharCodeToUnicode::mapToCharCode(Unicode* u, CharCode *c, int usize) { + //FIXME: .. + //look for charcode in map + if (usize == 1) { +/* printf("CharCodeToUnicode::mapToCharCode: 0x%x \n", u&0xff);*/ + for (int i=0; i #endif struct CharCodeToUnicodeString; //------------------------------------------------------------------------ class CharCodeToUnicode { +friend class UnicodeToCharCode; public: // Read the CID-to-Unicode mapping for from the file // specified by . Sets the initial reference count to 1. // Returns NULL on failure. static CharCodeToUnicode *parseCIDToUnicode(GooString *fileName, GooString *collection); @@ -61,16 +62,18 @@ public: GBool match(GooString *tagA); // Set the mapping for . void setMapping(CharCode c, Unicode *u, int len); // Map a CharCode to Unicode. int mapToUnicode(CharCode c, Unicode *u, int size); + int mapToCharCode(Unicode* u, CharCode *c, int usize); + // Return the mapping's length, i.e., one more than the max char // code supported by the mapping. CharCode getLength() { return mapLen; } private: void parseCMap1(int (*getCharFunc)(void *), void *data, int nBits); void addMapping(CharCode code, char *uStr, int n, int offset); Index: poppler/Dict.cc =================================================================== RCS file: /cvs/poppler/poppler/poppler/Dict.cc,v retrieving revision 1.5 diff -u -8 -p -B -r1.5 Dict.cc --- poppler/Dict.cc 3 Sep 2006 09:27:21 -0000 1.5 +++ poppler/Dict.cc 28 Jan 2007 21:06:48 -0000 @@ -60,16 +60,48 @@ inline DictEntry *Dict::find(const UGooS for (i = 0; i < length; ++i) { if (!key.cmp(entries[i].key)) return &entries[i]; } return NULL; } +void Dict::remove(const UGooString &key) { + int i; + bool found = false; + DictEntry tmp; + if(length == 0) return; + + for(i=0; ival.free(); + e->val = *val; + } else { + add (key, val); + } +} + + GBool Dict::is(char *type) { DictEntry *e; return (e = find("Type")) && e->val.isName(type); } Object *Dict::lookup(const UGooString &key, Object *obj) { DictEntry *e; Index: poppler/Dict.h =================================================================== RCS file: /cvs/poppler/poppler/poppler/Dict.h,v retrieving revision 1.4 diff -u -8 -p -B -r1.4 Dict.h --- poppler/Dict.h 3 Sep 2006 09:27:21 -0000 1.4 +++ poppler/Dict.h 28 Jan 2007 21:06:48 -0000 @@ -45,16 +45,20 @@ public: void addOwnKeyVal(UGooString *key, Object *val); // FIXME: should also be renamed to addOwnVal() void add(const UGooString &key, Object *val) { addOwnKeyVal(new UGooString(key), val); } void addOwnVal(const char *key, Object *val) { addOwnKeyVal(new UGooString(key), val); } + // Update the value of an existing entry, otherwise create it + void set(const UGooString &key, Object *val); + // Remove an entry. This invalidate indexes + void remove(const UGooString &key); // Check if dictionary is of specified type. GBool is(char *type); // Look up an entry and return the value. Returns a null object // if is not in the dictionary. Object *lookup(const UGooString &key, Object *obj); Object *lookupNF(const UGooString &key, Object *obj); Index: poppler/Form.cc =================================================================== RCS file: poppler/Form.cc diff -N poppler/Form.cc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ poppler/Form.cc 28 Jan 2007 21:06:49 -0000 @@ -0,0 +1,1048 @@ +//======================================================================== +// +// Form.cc +// +// Copyright 2006 Julien Rebetez +// +//======================================================================== + +#include + +#ifdef USE_GCC_PRAGMAS +#pragma implementation +#endif + +#include +#include +#include "goo/gmem.h" +#include "goo/GooString.h" +#include "Error.h" +#include "Object.h" +#include "Array.h" +#include "Dict.h" +#include "Form.h" +#include "UGooString.h" +#include "XRef.h" +#include "PDFDocEncoding.h" +#include "Annot.h" +#include "Catalog.h" + +//return a newly allocated char* containing an UTF16BE string of size length +static char* pdfDocEncodingToUTF16 (GooString* orig, int* length) +{ + //double size, a unicode char takes 2 char, add 2 for the unicode marker + *length = 2+2*orig->getLength(); + char *result = new char[(*length)]; + char *cstring = orig->getCString(); + //unicode marker + result[0] = 0xfe; + result[1] = 0xff; + //convert to utf16 + for(int i=2,j=0; i<(*length); i+=2,j++) { + Unicode u = pdfDocEncoding[(unsigned int)((unsigned char)cstring[j])]&0xffff; + result[i+1] = *(char*)(&u); + result[i] = *(1+(char*)(&u)); + } + return result; +} + + + +FormWidget::FormWidget(XRef *xrefA, Object *aobj, unsigned num, Ref aref) +{ + Object obj1, obj2; + ref = aref; + double t; + ID = 0; + fontSize = 0.0; + childNum = num; + xref = xrefA; + aobj->copy(&obj); + type = formUndef; + Dict *dict = obj.getDict(); + + if (!dict->lookup("Rect", &obj1)->isArray()) { + error(-1, "Annotation rectangle is wrong type"); + goto err2; + } + if (!obj1.arrayGet(0, &obj2)->isNum()) { + error(-1, "Bad annotation rectangle"); + goto err1; + } + x1 = obj2.getNum(); + obj2.free(); + if (!obj1.arrayGet(1, &obj2)->isNum()) { + error(-1, "Bad annotation rectangle"); + goto err1; + } + y1 = obj2.getNum(); + obj2.free(); + if (!obj1.arrayGet(2, &obj2)->isNum()) { + error(-1, "Bad annotation rectangle"); + goto err1; + } + x2 = obj2.getNum(); + obj2.free(); + if (!obj1.arrayGet(3, &obj2)->isNum()) { + error(-1, "Bad annotation rectangle"); + goto err1; + } + y2 = obj2.getNum(); + obj2.free(); + obj1.free(); + //swap coords if needed + if (x1 > x2) { + t = x1; + x1 = x2; + x2 = t; + } + if (y1 > y2) { + t = y1; + y1 = y2; + y2 = t; + } + + err1: + obj2.free(); + err2: + obj1.free(); +} + +FormWidget::FormWidget(FormWidget *dest) +{ + x1 = dest->x1; + y1 = dest->y1; + x2 = dest->x2; + y2 = dest->x2; + + type = dest->type; + +} + +FormWidget::~FormWidget() +{ + +} + +int FormWidget::encodeID (unsigned pageNum, unsigned fieldNum) +{ + return (pageNum << 4*sizeof(unsigned)) + fieldNum; +} + +void FormWidget::decodeID (unsigned id, unsigned* pageNum, unsigned* fieldNum) +{ + *pageNum = id >> 4*sizeof(unsigned); + *fieldNum = (id << 4*sizeof(unsigned)) >> 4*sizeof(unsigned); +} + +FormWidgetButton::FormWidgetButton (XRef *xrefA, Object *aobj, unsigned num, Ref ref, FormFieldButton *p) : FormWidget(xrefA, aobj, num, ref) +{ + parent = p; + type = formButton; + onStr = NULL; + Dict *dict = obj.getDict(); + Object obj1; + state = gFalse; + siblingsID = NULL; + + //find the name of the state in the AP dictionnary (/Yes, /Off) + //The reference say the Off state, if it existe, _must_ be stored in the AP dict under the name /Off + //The "on" state may be stored under any other name + if (dict->lookup("AP", &obj1)->isDict()) { + Dict *tmpDict = obj1.getDict(); + int length = tmpDict->getLength(); + for(int i=0; igetVal(i, &obj2); + if (obj2.isDict()) { + Dict *tmpDict2 = obj2.getDict(); + int length2 = tmpDict2->getLength(); + for(int j=0; jgetVal(j, &obj3); + UGooString *key = tmpDict2->getKey(j); + if(strcmp(key->getCString(), "Off")) { //if we don't have Off, we have the name of the "on" state + onStr = strdup(key->getCString()); + } + obj3.free(); + } + } else if (obj2.isStream()) { + Stream *str = obj2.getStream(); + Dict *tmpDict2 = str->getDict(); + Object obj3; + tmpDict2->lookup("Length", &obj3); + int c; + onStr = "D"; + } else if (obj2.isName()) { + printf("\t\tFormWidgetButton: AP name: %s\n", obj2.getName()); + } + obj2.free(); + } + } + obj1.free(); + + //We didn't found the "on" state for the button + if (!onStr) { + error(-1, "FormFieldButton:: unable to find the on state for the button\n"); + } +} + +FormWidgetButton::~FormWidgetButton () +{ + if (siblingsID) + gfree(siblingsID); +} + +void FormWidgetButton::setState (GBool astate, GBool calledByParent) +{ + //notify parent + //the state modification may be denied by the parent. e.g we don't want to let the user put all combo boxes to false + if (!calledByParent) { //avoid infinite recursion + if (!parent->setState(childNum, astate)) { + printf("setState denied by parent\n"); + return; + } + } + printf("---------- setState : %i (ref %i %i) \n", astate, ref.num, ref.gen); + state = astate; + //update appearance + char *offStr = "Off"; + Object *obj1 = new Object(); + obj1->initName(state?onStr:offStr); + obj.getDict()->set("V", obj1); + obj1 = new Object(); + obj1->initName(state?onStr:offStr); + //modify the Appearance State entry as well + obj.getDict()->set("AS", obj1); + + //notify the xref about the update + xref->setModifiedObject(&obj, ref); +} + +bool FormWidgetButton::isReadOnly() const +{ + return parent->isReadOnly(); +} + +void FormWidgetButton::loadDefaults () +{ + Dict *dict = obj.getDict(); + Object obj1; + + if (dict->lookup("V", &obj1)->isName()) { + char *s = obj1.getName(); + if(strcmp(s, "Off")) { + //state = gTrue; + setState(gTrue); + } + } else if (obj1.isArray()) { //handle the case where we have multiple choices + error(-1, "FormWidgetButton:: multiple choice isn't supported yet\n"); + } +} + +GBool FormWidgetButton::getState () +{ + return state; +} + +void FormWidgetButton::setNumSiblingsID (int i) +{ + numSiblingsID = i; + siblingsID = (unsigned*)greallocn(siblingsID, numSiblingsID, sizeof(unsigned)); +} + + +FormWidgetText::FormWidgetText (XRef *xrefA, Object *aobj, unsigned num, Ref ref, FormFieldText *p) : FormWidget(xrefA, aobj, num, ref) +{ + parent = p; + type = formText; + Dict *dict = aobj->getDict(); +} + +void FormWidgetText::loadDefaults () +{ + Dict *dict = obj.getDict(); + Object obj1; + + if (dict->lookup("V", &obj1)->isString()) { + if (obj1.getString()->hasUnicodeMarker()) { + if (obj1.getString()->getLength() <= 2) { + //printf("%s:%i, skipped empty unicode string\n", __FILE__, __LINE__); + } else { + parent->setContentCopy(obj1.getString()); + } + } else { + if (obj1.getString()->getLength() > 0) { + //printf("Form:cc:%i, found non-unicode string\n", __LINE__); + //non-unicode string -- assume pdfDocEncoding and try to convert to UTF16BE + int tmp_length; + char* tmp_str = pdfDocEncodingToUTF16(obj1.getString(), &tmp_length); + GooString* str1 = new GooString(tmp_str, tmp_length); + parent->setContentCopy(str1); + delete str1; + } + } + } + obj1.free(); + +} + +GooString* FormWidgetText::getContent () +{ + return parent->getContent(); +} + +GooString* FormWidgetText::getContentCopy () +{ + return parent->getContentCopy(); +} + +bool FormWidgetText::isMultiline () const +{ + return parent->isMultiline(); +} + +bool FormWidgetText::isPassword () const +{ + return parent->isPassword(); +} + +bool FormWidgetText::isFileSelect () const +{ + return parent->isFileSelect(); +} + +bool FormWidgetText::noSpellCheck () const +{ + return parent->noSpellCheck(); +} + +bool FormWidgetText::noScroll () const +{ + return parent->noScroll(); +} + +bool FormWidgetText::isComb () const +{ + return parent->isComb(); +} + +bool FormWidgetText::isRichText () const +{ + return parent->isRichText(); +} + +bool FormWidgetText::isReadOnly () const +{ + return parent->isReadOnly(); +} + +void FormWidgetText::setContentCopy(GooString* new_content) +{ + if (new_content == NULL) { + parent->setContentCopy(NULL); + } else { + char *tmp = new char[new_content->getLength()]; + char *cstring = new_content->getCString(); + int length = new_content->getLength(); + //ugly hack to convert UTF16LE to UTF16BE (since g_convert doesn't add a bit marker to UTF16BE + // because big endian is the default unicode encoding). + for (int i=0; isetContentCopy(cont); + Object *obj1 = new Object(); + obj1->initString(cont); + obj.getDict()->set("V", obj1); + //notify the xref about the update + xref->setModifiedObject(&obj, ref); + + } +} + +FormWidgetChoice::FormWidgetChoice(XRef *xrefA, Object *aobj, unsigned num, Ref ref, FormFieldChoice *p) : FormWidget(xrefA, aobj, num, ref) +{ + parent = p; + type = formChoice; +} + +void FormWidgetChoice::loadDefaults () +{ + Dict *dict = obj.getDict(); + Object obj1; + if (dict->lookup("Opt", &obj1)->isArray()) { + Object obj2; + parent->_setNumChoices(obj1.arrayGetLength()); + parent->_setChoices(new GooString*[parent->getNumChoices()]); + for(int i=0; igetNumChoices(); i++) { + obj1.arrayGet(i, &obj2); + if(obj2.isString()) { + parent->_setChoice(i, obj2.getString()->copy()); + } else if (obj2.isArray()) { // [Export_value, Displayed_text] + printf("FormWidgetChoice::array\n"); + } else { + error(-1, "FormWidgetChoide:: invalid Opt entry\n"); + } + obj2.free(); + } + } else { + //empty choice + } + obj1.free(); + + int tmpCurrentChoice = -1; + if (dict->lookup("V", &obj1)->isString()) { //FIXME: this may be an array too + printf("found default choice : %s\n", obj1.getString()->getCString()); + for(int i=0; igetNumChoices(); i++) { + if (parent->getChoice(i)->cmp(obj1.getString()) == 0) { + tmpCurrentChoice = i; + break; + } + } + } + obj1.free(); + + //convert choice's human readable strings to UTF16 + //and update the /Opt dict entry to reflect this change + #ifdef UPDATE_OPT + Object *objOpt = new Object(); + objOpt->initArray(xref); + #endif + for(int i=0; igetNumChoices(); i++) { + if (parent->getChoice(i)->hasUnicodeMarker()) { //string already in UTF16, do nothing + + } else { //string in pdfdocencoding, convert to UTF16 + int len; + char* buffer = pdfDocEncodingToUTF16(parent->getChoice(i), &len); + parent->getChoice(i)->Set(buffer, len); + delete [] buffer; + } + #ifdef UPDATE_OPT + Object *obj2 = new Object(); + obj2->initString(choices[i]); + objOpt->getArray()->add(obj2); + #endif + } + //set default choice now that we have UTF16 strings + if (tmpCurrentChoice != -1) + parent->setChoice(tmpCurrentChoice); + #ifdef UPDATE_OPT + obj.getDict()->set("Opt", objOpt); + xref->setModifiedObject(&obj, ref); + #endif +} + +FormWidgetChoice::~FormWidgetChoice() +{ +} + +void FormWidgetChoice::setChoice(int i) +{ + if (i < 0 || i >= parent->getNumChoices()) { + error(-1, "FormWidgetChoice::setChoice i out of range : %i", i); + return; + } + //notify parent + parent->setChoice(i); + //update appearance + Object *obj1 = new Object(); + obj1->initString(new GooString(parent->getChoice(parent->getSelected()))); + obj.getDict()->set("V", obj1); + + //notify the xref about the update + xref->setModifiedObject(&obj, ref); +} + +int FormWidgetChoice::getNumChoices() +{ + return parent->getNumChoices(); +} + +GooString* FormWidgetChoice::getChoice(int i) +{ + return parent->getChoice(i); +} + +int FormWidgetChoice::getSelected () +{ + return parent->getSelected(); +} + + +bool FormWidgetChoice::isCombo () const +{ + return parent->isCombo(); +} + +bool FormWidgetChoice::hasEdit () const +{ + return parent->hasEdit(); +} + +bool FormWidgetChoice::isMultiSelect () const +{ + return parent->isMultiSelect(); +} + +bool FormWidgetChoice::noSpellCheck () const +{ + return parent->noSpellCheck(); +} + +bool FormWidgetChoice::commitOnSelChange () const +{ + return parent->commitOnSelChange(); +} + +bool FormWidgetChoice::isReadOnly () const +{ + return parent->isReadOnly(); +} + +bool FormWidgetChoice::isListBox () const +{ + return parent->isListBox(); +} + +FormWidgetSignature::FormWidgetSignature(XRef *xrefA, Object *aobj, unsigned num, Ref ref, FormFieldSignature *p) : FormWidget(xrefA, aobj, num, ref) +{ + parent = p; + type = formSignature; +} + +bool FormWidgetSignature::isReadOnly () const +{ + return parent->isReadOnly(); +} + +//======================================================================== +// FormField +//======================================================================== + +FormField::FormField(XRef* xrefA, Object *aobj, Ref *aref, Form* aform, FormFieldType ty) +{ + double t; + xref = xrefA; + aobj->copy(&obj); + Dict* dict = obj.getDict(); + ref.num = ref.gen = 0; + type = ty; + numChildren = 0; + children = NULL; + terminal = false; + widgets = NULL; + readOnly = false; + form = aform; + + if (!aref) { + direct = true; + } else { + ref = *aref; + direct = false; + } + + Object obj1; + //childs + if (dict->lookup("Kids", &obj1)->isArray()) { + Array *array = obj1.getArray(); + int length = array->getLength(); + // Load children + for(int i=0; iget(i, &obj2); + array->getNF(i, &childRef); + //field child + if(obj2.dictLookup("FT", &obj3)->isName()) { + if(terminal) error(-1, "Field can't have both Widget AND Field as kids\n"); + + numChildren++; + children = (FormField**)greallocn(children, numChildren, sizeof(FormField*)); + + obj3.free(); + form->createFieldFromDict (&obj2, &children[numChildren-1], xrefA, &childRef.getRef()); + } + // 1 - we will handle 'collapsed' fields (field + annot in the same dict) + // as if the annot was in the Kids array of the field + else if (obj2.dictLookup("Subtype",&obj3)->isName()) { + _createWidget(&obj2, childRef.getRef()); + } + obj2.free(); + obj3.free(); + } + } + obj1.free(); + // As said in 1, if this is a 'collapsed' field, behave like if we had a + // child annot + if (dict->lookup("Subtype", &obj1)->isName()) { + if (direct) { + error(-1, "direct annot object\n"); + } + _createWidget(aobj, ref); + } + obj1.free(); + + //flags + if (dict->lookup("Ff", &obj1)->isInt()) { + int flags = obj1.getInt(); + if (flags & 0x2) { // 2 -> Required + //TODO + printf("Required field\n"); + } + if (flags & 0x4) { // 3 -> NoExport + //TODO + printf("NoExport field\n"); + } + } + obj1.free(); + +} + +/*FormField::FormField(FormField *dest) +{ + type = dest->type; +}*/ + +FormField::~FormField() +{ + if(children) + gfree(children); + obj.free(); +} + +void FormField::loadChildrenDefaults () +{ + if(!terminal) { + for(int i=0; iloadChildrenDefaults(); + } + } else { + for (int i=0; iloadDefaults(); + } + } +} + +void FormField::fillChildrenSiblingsID() +{ + if(terminal) return; + for (int i=0; iloadChildrenDefaults(); + } +} + + +void FormField::_createWidget (Object *obj, Ref aref) +{ + terminal = true; + numChildren++; + widgets = (FormWidget**)greallocn(widgets, numChildren, sizeof(FormWidget*)); + //ID = index in "widgets" table + if(type==formButton) widgets[numChildren-1] = new FormWidgetButton(xref, obj, numChildren-1, aref, static_cast(this)); + else if (type==formText) widgets[numChildren-1] = new FormWidgetText(xref, obj, numChildren-1, aref, static_cast(this)); + else if (type==formChoice) widgets[numChildren-1] = new FormWidgetChoice(xref, obj, numChildren-1, aref, static_cast(this)); + else if (type==formSignature) widgets[numChildren-1] = new FormWidgetSignature(xref, obj, numChildren-1, aref, static_cast(this)); + else { + error(-1, "SubType on non-terminal field, invalid document?"); + numChildren--; + terminal = false; + } +} + +FormWidget* FormField::findWidgetByRef (Ref aref) +{ + if (terminal) { + for(int i=0; igetRef().num == aref.num + && widgets[i]->getRef().gen == aref.gen) + return widgets[i]; + } + } else { + for(int i=0; ifindWidgetByRef(aref); + if(result) return result; + } + } + return NULL; +} + + +//------------------------------------------------------------------------ +// FormFieldButton +//------------------------------------------------------------------------ +FormFieldButton::FormFieldButton(XRef *xrefA, Object *aobj, Ref *ref, Form* form) + : FormField(xrefA, aobj, ref, form, formButton) +{ + type = formButton; + Dict* dict = obj.getDict(); + active_child = -1; + + + Object obj1; + btype = formButtonCheck; + if (dict->lookup("Ff", &obj1)->isInt()) { + int flags = obj1.getInt(); + if (flags & 0x10000) { // 17 -> push button + btype = formButtonPush; + } else if (flags & 0x8000) { // 16 -> radio button + btype = formButtonRadio; + if (flags & 0x4000) { // 15 -> noToggleToOff + //TODO + } + } + if (flags & 0x1000000) { // 26 -> radiosInUnison + error(-1, "FormFieldButton:: radiosInUnison flag unimplemented, please report a bug with a testcase\n"); + } + } + obj1.free(); +} + +void FormFieldButton::fillChildrenSiblingsID() +{ + for(int i=0; i(widgets[i]); + btn->setNumSiblingsID(numChildren-1); + for(int j=0, counter=0; jsetSiblingsID(counter, widgets[j]->getID()); + counter++; + } + } +} + +GBool FormFieldButton::setState (int num, GBool s) +{ + if (readOnly) { + error(-1, "FormFieldButton::setState called on a readOnly field\n"); + return gFalse; + } + + if(btype == formButtonRadio) { + if (s == gTrue) { + active_child = num; + for(int i=0; i(widgets[i])->setState(gFalse, gTrue); + } + + //The parent field's V entry holds a name object corresponding to the ap- + //pearance state of whichever child field is currently in the on state + if (active_child >= 0) { + FormWidgetButton* actChild = static_cast(widgets[active_child]); + if (actChild->getOnStr()) { + Object *obj1 = new Object(); + obj1->initName(actChild->getOnStr()); + obj.getDict()->set("V", obj1); + xref->setModifiedObject(&obj, ref); + } + } + } else { + return gFalse; //don't allow to set all radio to false + } + } + return gTrue; +} + +FormFieldButton::~FormFieldButton() +{ +} + +//------------------------------------------------------------------------ +// FormFieldText +//------------------------------------------------------------------------ +FormFieldText::FormFieldText(XRef *xrefA, Object *aobj, Ref *ref, Form* form) + : FormField(xrefA, aobj, ref, form, formText) +{ + type = formText; + Dict* dict = obj.getDict(); + Object obj1; + content = NULL; + multiline = password = fileSelect = doNotSpellCheck = doNotScroll = comb = richText = false; + + if (dict->lookup("Ff", &obj1)->isInt()) { + int flags = obj1.getInt(); + if (flags & 0x1000) // 13 -> Multiline + multiline = true; + if (flags & 0x2000) // 14 -> Password + password = true; + if (flags & 0x100000) // 21 -> FileSelect + fileSelect = true; + if (flags & 0x400000)// 23 -> DoNotSpellCheck + doNotSpellCheck = true; + if (flags & 0x800000) // 24 -> DoNotScroll + doNotScroll = true; + if (flags & 0x1000000) // 25 -> Comb + comb = true; + if (flags & 0x2000000)// 26 -> RichText + richText = true; + } + obj1.free(); +} + +GooString* FormFieldText::getContentCopy () +{ + if (!content) return NULL; + return new GooString(*content); +} + +void FormFieldText::setContentCopy (GooString* new_content) +{ + if (readOnly) { + error(-1, "FormFieldText::setContentCopy called on a readOnly field\n"); + return; + } + + if(content) { + delete content; + } + content = new_content->copy(); +} + +FormFieldText::~FormFieldText() +{ + +} + + +//------------------------------------------------------------------------ +// FormFieldChoice +//------------------------------------------------------------------------ +FormFieldChoice::FormFieldChoice(XRef *xrefA, Object *aobj, Ref *ref, Form* form) + : FormField(xrefA, aobj, ref, form, formChoice) +{ + numChoices = 0; + choices = NULL; + currentChoice = -1; + + Dict* dict = obj.getDict(); + Object obj1; + + combo = edit = multiselect = doNotSpellCheck = doCommitOnSelChange = false; + + if (dict->lookup("Ff", &obj1)->isInt()) { + int flags = obj1.getInt(); + if (flags & 0x20000) // 18 -> Combo + combo = true; + if (flags & 0x40000) // 19 -> Edit + edit = true; + if (flags & 0x200000) // 22 -> MultiSelect + multiselect = true; + if (flags & 0x400000) // 23 -> DoNotSpellCheck + doNotSpellCheck = true; + if (flags & 0x4000000) // 27 -> CommitOnSelChange + doCommitOnSelChange = true; + } + obj1.free(); + +} + +FormFieldChoice::~FormFieldChoice() +{ + delete [] choices; +} + +void FormFieldChoice::setChoice (int i) +{ + printf("setChoice: %i\n", i); + currentChoice = i; +} + +//------------------------------------------------------------------------ +// FormFieldSignature +//------------------------------------------------------------------------ +FormFieldSignature::FormFieldSignature(XRef *xrefA, Object *dict, Ref *ref, Form* form) + : FormField(xrefA, dict, ref, form, formSignature) +{ +} + +FormFieldSignature::~FormFieldSignature() +{ + +} + +//------------------------------------------------------------------------ +// Form +//------------------------------------------------------------------------ + +Form::Form(XRef *xrefA, Object* acroForm) +{ + Object obj1; + xref = xrefA; + Array *array = acroForm->dictLookup("Fields",&obj1)->getArray(); + obj1.free(); + if(!array) { + error(-1, "Can't get Fields array\n"); + } + size = 0; + numFields = 0; + rootFields = NULL; + for(int i=0; igetLength(); i++) { + Object oref; + Ref* pref; + array->get(i, &obj1); + array->getNF(i, &oref); + if (!oref.isRef()) pref = NULL; + else pref = &oref.getRef(); + + if (numFields >= size) { + size += 16; + rootFields = (FormField**)greallocn(rootFields,size,sizeof(FormField*)); + } + + createFieldFromDict (&obj1, &rootFields[numFields++], xrefA, pref); + + //Mark readonly field + Object obj3; + if (obj1.dictLookup("Ff", &obj3)->isInt()) { + int flags = obj3.getInt(); + if (flags & 0x1) + rootFields[numFields-1]->setReadOnly(true); + } + obj3.free(); + + obj1.free(); + oref.free(); + } + + checkForNeedAppearances(); +} + +Form::~Form() { + int i; + for(i = 0; i< numFields; ++i) + delete rootFields[i]; + delete [] rootFields; +} + +void Form::checkForNeedAppearances () +{ + //NeedAppearances needs to be set to 'true' in the AcroForm entry of the Catalog to enable dynamic appearance generation + Object* catalog = new Object(); + Object acroForm; + Ref catRef; + catRef.gen = xref->getRootGen(); + catRef.num = xref->getRootNum(); + catalog = xref->getCatalog(catalog); + catalog->dictLookup("AcroForm", &acroForm); + Object obj1; + obj1.initBool(true); + acroForm.dictSet("NeedAppearances", &obj1); + obj1.free(); + catalog->dictSet("AcroForm", &acroForm); + xref->setModifiedObject(catalog, catRef); +} + + +void Form::createFieldFromDict (Object* obj, FormField** ptr, XRef *xrefA, Ref* pref) +{ + Object obj2; + if(obj->dictLookup("FT", &obj2)->isName("Btn")) { + (*ptr) = new FormFieldButton(xrefA, obj, pref, this); + } else if (obj2.isName("Tx")) { + (*ptr) = new FormFieldText(xrefA, obj, pref, this); + } else if (obj2.isName("Ch")) { + (*ptr) = new FormFieldChoice(xrefA, obj, pref, this); + } else if (obj2.isName("Sig")) { + (*ptr) = new FormFieldSignature(xrefA, obj, pref, this); + } else { //we don't have an FT entry => non-terminal field + (*ptr) = new FormField(xrefA, obj, pref, this); + } + obj2.free(); + (*ptr)->loadChildrenDefaults(); +} + +void Form::postWidgetsLoad () +{ + for(int i=0; ifillChildrenSiblingsID(); + } +} + +FormWidget* Form::findWidgetByRef (Ref aref) +{ + for(int i=0; ifindWidgetByRef(aref); + if(result) return result; + } + return NULL; +} + +//------------------------------------------------------------------------ +// FormPageWidgets +//------------------------------------------------------------------------ + +FormPageWidgets::FormPageWidgets (XRef *xrefA, Object* annots, unsigned int page, Form *form) +{ + Object obj1; + numWidgets = 0; + widgets = NULL; + if (annots->isArray() && form) { + size = annots->arrayGetLength(); + widgets = (FormWidget**)greallocn(widgets, size, sizeof(FormWidget*)); + + /* For each entry in the page 'Annots' dict, try to find + a matching form field */ + for (int i = 0; i < size; ++i) { + if (!annots->arrayGetNF(i, &obj1)->isRef()) { + /* Since all entry in a form field's kid dict needs to be + indirect references, if this annot isn't indirect, it isn't + related to a form field */ + obj1.free(); + continue; + } + Ref r = obj1.getRef(); + + /* Try to find a form field which either has this Annot in its Kids entry + or is merged with this Annot */ + FormWidget* tmp = form->findWidgetByRef(r); + if(tmp) { + // We've found a corresponding form field, link it + tmp->setID(FormWidget::encodeID(page, numWidgets)); + widgets[numWidgets++] = tmp; + //create a temporary Annot to get the font size + Object obj2; + if (annots->arrayGet(i, &obj2)->isDict()) { + Annot* ann = new Annot(xref, NULL ,obj2.getDict(), NULL, NULL); + tmp->setFontSize(ann->getFontSize()); + delete ann; + } + obj2.free(); + } + + obj1.free(); + } + } +} + +#if 0 +FormWidget *FormPageWidgets::find(double x, double y) const { + /*int i; + for (i = numWidgets -1; i>= 0; --i) { + if (widgets[i]->inRect(x, y)) { + return widgets[i]; + } + } + return NULL;*/ +} + +FormWidget* FormPageWidgets::getWidgetById (unsigned int id) const { + /*int i; + for (i=0; igetID() == id) { + return widgets[i]; + } + } + return NULL;*/ +} +GBool FormPageWidgets::onWidget(double x, double y) const { + /*int i; + for (i =0; iinRect(x,y)) + return gTrue; + } + return gFalse;*/ +} +#endif Index: poppler/Form.h =================================================================== RCS file: poppler/Form.h diff -N poppler/Form.h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ poppler/Form.h 28 Jan 2007 21:06:50 -0000 @@ -0,0 +1,426 @@ +//======================================================================== +// +// Form.h +// +// Copyright 2006 Julien Rebetez +// +//======================================================================== + +#ifndef FORM_H +#define FORM_H + +#ifdef USE_GCC_PRAGMAS +#pragma interface +#endif + +#include "Object.h" + +class GooString; +class UGooString; +class Array; +class Dict; +class Annot; +class Catalog; + +//------------------------------------------------------------------------ +// FormFieldType +//------------------------------------------------------------------------ + +enum FormFieldType { + formButton, + formText, + formChoice, + formSignature, + formUndef, + //kid type + formButtonCheck, + formButtonPush, + formButtonRadio +}; + +class Form; +class FormField; +class FormFieldButton; +class FormFieldText; +class FormFieldSignature; +class FormFieldChoice; + +//------------------------------------------------------------------------ +// FormWidget +//------------------------------------------------------------------------ + +class FormWidget { +public: + virtual ~FormWidget(); + + // see the description of FormField::LoadChildrenDefaults + virtual void loadDefaults () {} + + // Check if point is inside the field bounding rect + GBool inRect(double x, double y) + { return x1 <= x && x <= x2 && y1 <= y && y <= y2; } + + // Get the field bounding rect + void getRect(double *xa1, double *ya1, double *xa2, double *ya2) + { *xa1 = x1; *ya1 = y1; *xa2 = x2; *ya2 = y2; } + + unsigned getID () { return ID; } + void setID (unsigned int i) { ID=i; } + + FormFieldType getType() { return type; } + + Object* getObj() { return &obj; } + Ref getRef() { return ref; } + + void setChildNum (unsigned i) { childNum = i; } + unsigned getChildNum () { return childNum; } + + void setFontSize(double f) { fontSize = f; } + double getFontSize () { return fontSize; } + + virtual bool isReadOnly() const = 0; + + // return the unique ID corresponding to pageNum/fieldNum + static int encodeID (unsigned pageNum, unsigned fieldNum); + // decode id and retrieve pageNum and fieldNum + static void decodeID (unsigned id, unsigned* pageNum, unsigned* fieldNum); + +protected: + FormWidget(XRef *xrefA, Object *aobj, unsigned num, Ref aref); + FormWidget(FormWidget *dest); + + FormField* field; + FormFieldType type; + Object obj; + Ref ref; + XRef *xref; + //index of this field in the parent's child list + unsigned childNum; + + /* + Field ID is a integer, calculated as follow : + the first sizeof/2 bits are the field number, relative to the page + the last sizeof/2 bits are the page number + [page number | field number] + (encoding) id = (pageNum << 4*sizeof(unsigned)) + fieldNum; + (decoding) pageNum = id >> 4*sizeof(unsigned); fieldNum = (id << 4*sizeof(unsigned)) >> 4*sizeof(unsigned); + */ + unsigned ID; + + double x1, y1; // lower left corner + double x2, y2; // upper right corner + double fontSize; //font size if this widget has text + +}; + +//------------------------------------------------------------------------ +// FormWidgetButton +//------------------------------------------------------------------------ + +class FormWidgetButton: public FormWidget { +public: + FormWidgetButton(XRef *xrefA, Object *dict, unsigned num, Ref ref, FormFieldButton *p); + ~FormWidgetButton (); + + void setState (GBool state, GBool calledByParent=gFalse); + GBool getState (); + + char* getOnStr () { return onStr; } + + void loadDefaults(); + + bool isReadOnly () const; + + void setNumSiblingsID (int i); + void setSiblingsID (int i, unsigned id) { siblingsID[i] = id; } + + unsigned* getSiblingsID () const { return siblingsID; } + int getNumSiblingsID () const { return numSiblingsID; } + +protected: + unsigned* siblingsID; // IDs of dependent buttons (each button of a radio field has all the others buttons + // of the same field in this array) + int numSiblingsID; + char *onStr; + FormFieldButton *parent; + GBool state; +}; + +//------------------------------------------------------------------------ +// FormWidgetText +//------------------------------------------------------------------------ + +class FormWidgetText: public FormWidget { +public: + FormWidgetText(XRef *xrefA, Object *dict, unsigned num, Ref ref, FormFieldText *p); + GooString* getContent() ; + GooString* getContentCopy(); + void setContentCopy(GooString* new_content); + + void loadDefaults (); + + bool isMultiline () const; + bool isPassword () const; + bool isFileSelect () const; + bool noSpellCheck () const; + bool noScroll () const; + bool isComb () const; + bool isRichText () const; + bool isReadOnly () const; +protected: + FormFieldText *parent; +}; + +//------------------------------------------------------------------------ +// FormWidgetChoice +//------------------------------------------------------------------------ + +class FormWidgetChoice: public FormWidget { +public: + FormWidgetChoice(XRef *xrefA, Object *dict, unsigned num, Ref ref, FormFieldChoice *p); + ~FormWidgetChoice(); + + void loadDefaults (); + int getNumChoices(); + GooString* getChoice(int i); + void setChoice(int i); + int getSelected (); + + bool isCombo () const; + bool hasEdit () const; + bool isMultiSelect () const; + bool noSpellCheck () const; + bool commitOnSelChange () const; + bool isReadOnly () const; + bool isListBox () const; +protected: + FormFieldChoice *parent; +}; + +//------------------------------------------------------------------------ +// FormWidgetSignature +//------------------------------------------------------------------------ + +class FormWidgetSignature: public FormWidget { +public: + FormWidgetSignature(XRef *xrefA, Object *dict, unsigned num, Ref ref, FormFieldSignature *p); + bool isReadOnly () const; +protected: + FormFieldSignature *parent; +}; + +//------------------------------------------------------------------------ +// FormField +//------------------------------------------------------------------------ + +class FormField { +public: + FormField(XRef* xrefa, Object *aobj, Ref *aref, Form* aform, FormFieldType t=formUndef); + + virtual ~FormField(); + + FormField *copy() { return new FormField(this); } + + // Accessors. + FormFieldType getType() { return type; } + Object* getObj() { return &obj; } + Ref getRef() { return ref; } + + void setReadOnly (bool b) { readOnly = b; } + bool isReadOnly () const { return readOnly; } + + FormWidget* findWidgetByRef (Ref aref); + // Since while loading their defaults, children may call parents methods, it's better + // to do that when parents are completly constructed + void loadChildrenDefaults(); + + // only implemented in FormFieldButton + virtual void fillChildrenSiblingsID (); + + + protected: + void _createWidget (Object *obj, Ref aref); + + FormFieldType type; // field type + Ref ref; + bool direct; + bool terminal; + Object obj; + XRef *xref; + FormField **children; + int numChildren; + FormWidget **widgets; + Form* form; + bool readOnly; + +private: + FormField() {} + FormField(FormField *dest); +}; + + +//------------------------------------------------------------------------ +// FormFieldButton +//------------------------------------------------------------------------ + +class FormFieldButton: public FormField { +public: + FormFieldButton(XRef *xrefA, Object *dict, Ref *ref, Form* form); + + FormFieldType getButtonType () { return btype; } + + // returns gTrue if the state modification is accepted + GBool setState (int num, GBool s); + + void fillChildrenSiblingsID (); + + virtual ~FormFieldButton(); +protected: + FormFieldType btype; + int size; + int active_child; //only used for combo box +}; + +//------------------------------------------------------------------------ +// FormFieldText +//------------------------------------------------------------------------ + +class FormFieldText: public FormField { +public: + FormFieldText(XRef *xrefA, Object *dict, Ref *ref, Form* form); + + GooString* getContent () { return content; } + GooString* getContentCopy (); + void setContentCopy (GooString* new_content); + virtual ~FormFieldText(); + + bool isMultiline () const { return multiline; } + bool isPassword () const { return password; } + bool isFileSelect () const { return fileSelect; } + bool noSpellCheck () const { return doNotSpellCheck; } + bool noScroll () const { return doNotScroll; } + bool isComb () const { return comb; } + bool isRichText () const { return richText; } +protected: + GooString* content; + bool multiline; + bool password; + bool fileSelect; + bool doNotSpellCheck; + bool doNotScroll; + bool comb; + bool richText; +}; + +//------------------------------------------------------------------------ +// FormFieldChoice +//------------------------------------------------------------------------ + +class FormFieldChoice: public FormField { +public: + FormFieldChoice(XRef *xrefA, Object *aobj, Ref *ref, Form* form); + + virtual ~FormFieldChoice(); + + int getNumChoices() { return numChoices; } + GooString* getChoice(int i) { return choices[i]; } + void setChoice(int i); + int getSelected () { return currentChoice; } + + bool isCombo () const { return combo; } + bool hasEdit () const { return edit; } + bool isMultiSelect () const { return multiselect; } + bool noSpellCheck () const { return doNotSpellCheck; } + bool commitOnSelChange () const { return doCommitOnSelChange; } + bool isListBox () const { return !combo; } + + /* these functions _must_ only be used by FormWidgetChoice */ + void _setNumChoices (int i) { numChoices = i; } + void _setChoices(GooString** c) { choices = c; } + void _setChoice(int i, GooString* str) { choices[i] = str; } + +protected: + bool combo; + bool edit; + bool multiselect; + bool doNotSpellCheck; + bool doCommitOnSelChange; + + int numChoices; + GooString **choices; + int currentChoice; +}; + +//------------------------------------------------------------------------ +// FormFieldSignature +//------------------------------------------------------------------------ + +class FormFieldSignature: public FormField { +public: + FormFieldSignature(XRef *xrefA, Object *dict, Ref *ref, Form* form); + + virtual ~FormFieldSignature(); +}; + +//------------------------------------------------------------------------ +// Form +// This class handle the document-wide part of Form (things in the acroForm +// Catalog entry). +//------------------------------------------------------------------------ + +class Form { +public: + Form(XRef *xrefA, Object* acroForm); + + ~Form(); + + int getNumFields() const { return numFields; } + FormField* getRootField(int i) const { return rootFields[i]; } + + FormWidget* findWidgetByRef (Ref aref); + + /* Creates a new Field of the type specified in obj's dict. + used in Form::Form and FormField::FormField */ + void createFieldFromDict (Object* obj, FormField** ptr, XRef *xref, Ref* pref); + + void postWidgetsLoad(); + void checkForNeedAppearances (); +private: + FormField** rootFields; + int numFields; + int size; + XRef* xref; + Catalog* catalog; +}; + +//------------------------------------------------------------------------ +// FormPageWidgets +//------------------------------------------------------------------------ + +class FormPageWidgets { +public: + FormPageWidgets (XRef *xrefA, Object* annots, unsigned int page, Form *form); + ~FormPageWidgets(); + + int getNumWidgets() const { return numWidgets; } + FormWidget* getWidget(int i) const { return widgets[i]; } + #if 0 + FormWidget* getWidgetById (unsigned int id) const; + + // If point , is in a field, return the associated field; + // else return NULL + FormWidget* find(double x, double y) const; + + // Return true if , is in a field. + GBool onWidget(double x, double y) const; + #endif +private: + FormWidget** widgets; + int numWidgets; + int size; + unsigned pageNum; + XRef* xref; +}; + +#endif + Index: poppler/GfxFont.cc =================================================================== RCS file: /cvs/poppler/poppler/poppler/GfxFont.cc,v retrieving revision 1.9 diff -u -8 -p -B -r1.9 GfxFont.cc --- poppler/GfxFont.cc 2 May 2006 04:38:39 -0000 1.9 +++ poppler/GfxFont.cc 28 Jan 2007 21:06:53 -0000 @@ -1577,16 +1577,43 @@ Gushort *GfxCIDFont::getCodeToGIDMap(FoF map[cidlen] = ff->mapCodeToGID(cmap, u); cidlen++; } *mapsizep = cidlen; return map; } +double GfxCIDFont::getWidth (char* s, int len) { + int nUsed; + double w, h, vx, vy; + int a, b, m; + + CID cid = cMap->getCID(s, len, &nUsed); + + w = widths.defWidth; + if (widths.nExceps > 0 && cid >= widths.exceps[0].first) { + a = 0; + b = widths.nExceps; + // invariant: widths.exceps[a].first <= cid < widths.exceps[b].first + while (b - a > 1) { + m = (a + b) / 2; + if (widths.exceps[m].first <= cid) { + a = m; + } else { + b = m; + } + } + if (cid <= widths.exceps[a].last) { + w = widths.exceps[a].width; + } + } + return w; +} + //------------------------------------------------------------------------ // GfxFontDict //------------------------------------------------------------------------ GfxFontDict::GfxFontDict(XRef *xref, Ref *fontDictRef, Dict *fontDict) { int i; Object obj1, obj2; Ref r; Index: poppler/GfxFont.h =================================================================== RCS file: /cvs/poppler/poppler/poppler/GfxFont.h,v retrieving revision 1.4 diff -u -8 -p -B -r1.4 GfxFont.h --- poppler/GfxFont.h 2 May 2006 04:38:39 -0000 1.4 +++ poppler/GfxFont.h 28 Jan 2007 21:06:54 -0000 @@ -120,17 +120,19 @@ public: // Get font tag. GooString *getTag() { return tag; } // Get font dictionary ID. Ref *getID() { return &id; } // Does this font match the tag? - GBool matches(char *tagA) { return !tag->cmp(tagA); } + // FIXME: added name comp in order to have scribus-exported PDF forms rendering + // correctly (form-scribus.pdf for example) + GBool matches(char *tagA) { return (!tag->cmp(tagA) || !name->cmp(tagA)); } // Get base font name. GooString *getName() { return name; } // Get font family name. GooString *getFamily() { return family; } // Get font stretch. @@ -162,16 +164,19 @@ public: // Get font descriptor flags. GBool isFixedWidth() { return flags & fontFixedWidth; } GBool isSerif() { return flags & fontSerif; } GBool isSymbolic() { return flags & fontSymbolic; } GBool isItalic() { return flags & fontItalic; } GBool isBold() { return flags & fontBold; } + // Return the Unicode map. + virtual CharCodeToUnicode *getToUnicode() = 0; + // Return the font matrix. double *getFontMatrix() { return fontMat; } // Return the font bounding box. double *getFontBBox() { return fontBBox; } // Return the ascent and descent values. double getAscent() { return ascent; } @@ -311,16 +316,18 @@ public: // Return the CID-to-GID mapping table. These should only be called // if type is fontCIDType2. Gushort *getCIDToGID() { return cidToGID; } int getCIDToGIDLen() { return cidToGIDLen; } Gushort *getCodeToGIDMap(FoFiTrueType *ff, int *length); + double getWidth(char* s, int len); + private: CMap *cMap; // char code --> CID CharCodeToUnicode *ctu; // CID --> Unicode GfxFontCIDWidths widths; // character widths Gushort *cidToGID; // CID --> GID mapping (for embedded // TrueType fonts) int cidToGIDLen; Index: poppler/Makefile.am =================================================================== RCS file: /cvs/poppler/poppler/poppler/Makefile.am,v retrieving revision 1.27 diff -u -8 -p -B -r1.27 Makefile.am --- poppler/Makefile.am 13 Jan 2007 18:29:39 -0000 1.27 +++ poppler/Makefile.am 28 Jan 2007 21:06:54 -0000 @@ -112,16 +112,17 @@ poppler_include_HEADERS = \ Catalog.h \ CharCodeToUnicode.h \ CMap.h \ Decrypt.h \ Dict.h \ Error.h \ FontEncodingTables.h \ FontInfo.h \ + Form.h \ Function.cc \ Function.h \ Gfx.h \ GfxFont.h \ GfxState.h \ GlobalParams.h \ JArithmeticDecoder.h \ JBIG2Stream.h \ @@ -174,16 +175,17 @@ libpoppler_la_SOURCES = \ BuiltinFontTables.cc \ Catalog.cc \ CharCodeToUnicode.cc \ CMap.cc \ Decrypt.cc \ Dict.cc \ Error.cc \ FontEncodingTables.cc \ + Form.cc \ FontInfo.cc \ Function.cc \ Gfx.cc \ GfxFont.cc \ GfxState.cc \ GlobalParams.cc \ JArithmeticDecoder.cc \ JBIG2Stream.cc \ Index: poppler/Object.h =================================================================== RCS file: /cvs/poppler/poppler/poppler/Object.h,v retrieving revision 1.3 diff -u -8 -p -B -r1.3 Object.h --- poppler/Object.h 3 Sep 2006 09:27:21 -0000 1.3 +++ poppler/Object.h 28 Jan 2007 21:06:55 -0000 @@ -166,16 +166,17 @@ public: Object *arrayGet(int i, Object *obj); Object *arrayGetNF(int i, Object *obj); // Dict accessors. int dictGetLength(); void dictAddOwnKeyVal(UGooString *key, Object *val); void dictAdd(const UGooString &key, Object *val); void dictAddOwnVal(const char *key, Object *val); + void dictSet(const UGooString &key, Object *val); GBool dictIs(char *dictType); Object *dictLookup(const UGooString &key, Object *obj); Object *dictLookupNF(const UGooString &key, Object *obj); UGooString *dictGetKey(int i); Object *dictGetVal(int i, Object *obj); Object *dictGetValNF(int i, Object *obj); // Stream accessors. @@ -249,16 +250,19 @@ inline void Object::dictAdd(const UGooSt { dict->add(key, val); } inline void Object::dictAddOwnVal(const char *key, Object *val) { dict->addOwnVal(key, val); } inline void Object::dictAddOwnKeyVal(UGooString *key, Object *val) { dict->addOwnKeyVal(key, val); } +inline void Object::dictSet(const UGooString &key, Object *val) + { dict->set(key, val); } + inline GBool Object::dictIs(char *dictType) { return dict->is(dictType); } inline GBool Object::isDict(char *dictType) { return type == objDict && dictIs(dictType); } inline Object *Object::dictLookup(const UGooString &key, Object *obj) { return dict->lookup(key, obj); } Index: poppler/PDFDoc.cc =================================================================== RCS file: /cvs/poppler/poppler/poppler/PDFDoc.cc,v retrieving revision 1.10 diff -u -8 -p -B -r1.10 PDFDoc.cc --- poppler/PDFDoc.cc 18 Jan 2006 22:32:13 -0000 1.10 +++ poppler/PDFDoc.cc 28 Jan 2007 21:06:57 -0000 @@ -120,16 +120,17 @@ PDFDoc::PDFDoc(wchar_t *fileNameA, int f file = NULL; str = NULL; xref = NULL; catalog = NULL; links = NULL; #ifndef DISABLE_OUTLINE outline = NULL; #endif + form = NULL; //~ file name should be stored in Unicode (?) fileName = new GooString(); for (i = 0; i < fileNameLen; ++i) { fileName->append((char)fileNameA[i]); } // zero-terminate the file name string @@ -196,25 +197,26 @@ GBool PDFDoc::setup(GooString *ownerPass } // check for encryption if (!checkEncryption(ownerPassword, userPassword)) { errCode = errEncrypted; return gFalse; } + // read catalog catalog = new Catalog(xref); if (!catalog->isOk()) { error(-1, "Couldn't read page catalog"); errCode = errBadCatalog; return gFalse; } -#ifndef DISABLE_OUTLINE + #ifndef DISABLE_OUTLINE // read outline outline = new Outline(catalog->getOutline(), xref); #endif // done return gTrue; } @@ -456,31 +458,290 @@ GBool PDFDoc::isLinearized() { obj4.free(); obj3.free(); obj2.free(); obj1.free(); delete parser; return lin; } -GBool PDFDoc::saveAs(GooString *name) { +GBool PDFDoc::saveAs(GooString *name, PDFWriteMode mode) { FILE *f; - int c; if (!(f = fopen(name->getCString(), "wb"))) { error(-1, "Couldn't open file '%s'", name->getCString()); return gFalse; } + + if (mode == writeForceRewrite) { + saveCompleteRewrite(f); + } else if (mode == writeForceIncremental) { + saveIncrementalUpdate(f); + } else { // let poppler decide + // find if we have updated objects + GBool updated = gFalse; + for(int i=0; igetNumObjects(); i++) { + if (xref->getEntry(i)->obj) { + updated = gTrue; + break; + } + } + if(updated) { + saveCompleteRewrite(f); + } else { + // simply copy the original file + int c; str->reset(); while ((c = str->getChar()) != EOF) { fputc(c, f); } str->close(); + } + } + fclose(f); return gTrue; } +void PDFDoc::saveIncrementalUpdate (FILE *f) +{ + XRef *uxref; + int c; + //copy the original file + str->reset(); + while ((c = str->getChar()) != EOF) { + fputc(c, f); + } + str->close(); + + uxref = new XRef(); + uxref->add(0, 65535, 0, gFalse); + int objectsCount = 0; //count the number of objects in the XRef(s) + for(int i=0; igetNumObjects(); i++) { + if ((xref->getEntry(i)->type == xrefEntryFree) && + (xref->getEntry(i)->gen == 0)) //we skip the irrelevant free objects + continue; + objectsCount++; + if (xref->getEntry(i)->obj) { //we have an updated object + Object obj1; + Ref ref; + ref.num = i; + ref.gen = xref->getEntry(i)->gen; + xref->fetch(ref.num, ref.gen, &obj1); + Guint offset = writeObject(&obj1, &ref, f); + uxref->add(ref.num, ref.gen, offset, gTrue); + obj1.free(); + } + } + if (uxref->getSize() == 0) { //we have nothing to update + delete uxref; + return; + } + + Guint uxrefOffset = ftell(f); + uxref->writeToFile(f); + + writeTrailer(uxrefOffset, objectsCount, f, gTrue); + + delete uxref; +} + +void PDFDoc::saveCompleteRewrite (FILE *f) +{ + fprintf(f, "%%PDF-%.1f\r\n",pdfVersion); + XRef *uxref = new XRef(); + uxref->add(0, 65535, 0, gFalse); + for(int i=0; igetNumObjects(); i++) { + Object obj1; + Ref ref; + XRefEntryType type = xref->getEntry(i)->type; + if (type == xrefEntryFree) { + ref.num = i; + ref.gen = xref->getEntry(i)->gen; + /* the XRef class add a lot of unrelevant free entries, we only want the significant one + and we don't want the one with num=0 because it has already been added (gen = 65535)*/ + if (ref.gen > 0 && ref.num > 0) + uxref->add(ref.num, ref.gen, 0, gFalse); + } else if (type == xrefEntryUncompressed){ + ref.num = i; + ref.gen = xref->getEntry(i)->gen; + xref->fetch(ref.num, ref.gen, &obj1); + Guint offset = writeObject(&obj1, &ref, f); + uxref->add(ref.num, ref.gen, offset, gTrue); + obj1.free(); + } else if (type == xrefEntryCompressed) { + ref.num = i; + ref.gen = 0; //compressed entries have gen == 0 + xref->fetch(ref.num, ref.gen, &obj1); + Guint offset = writeObject(&obj1, &ref, f); + uxref->add(ref.num, ref.gen, offset, gTrue); + obj1.free(); + } + } + Guint uxrefOffset = ftell(f); + uxref->writeToFile(f); + + writeTrailer(uxrefOffset, uxref->getSize(), f, gFalse); + + + delete uxref; + +} + +void PDFDoc::writeDictionnary (Dict* dict, FILE *f) +{ + Object obj1; + fprintf(f,"<< "); + for (int i=0; igetLength(); i++) { + fprintf(f,"/%s ", dict->getKey(i)->getCString()); + writeObject(dict->getValNF(i, &obj1), NULL, f); + fprintf(f,"\r\n"); + + obj1.free(); + } + fprintf(f,">>\r\n"); +} + +void PDFDoc::writeStream (Stream* str, FILE *f) +{ + int c; + fprintf(f,"stream\r\n"); + str->reset(); + for (int c=str->getChar(); c!= EOF; c=str->getChar()) { + fprintf(f,"%c", c); + } + fprintf(f,"\r\nendstream\r\n"); +} + +void PDFDoc::writeString (GooString* s, FILE* f) +{ + //write hexa string + const char* c = s->getCString(); + fprintf(f, "<"); + while(*c!='\0') { + fprintf(f, "%02x", *c); + c++; + } + fprintf(f, "> "); +} + +Guint PDFDoc::writeObject (Object* obj, Ref* ref, FILE *f) +{ + Array *array; + Object obj1; + Guint offset = ftell(f); + int tmp; + + if(ref) + fprintf(f,"%i %i obj\r\n", ref->num, ref->gen); + + switch (obj->getType()) { + case objBool: + fprintf(f,"%s ", obj->getBool()?"true":"false"); + break; + case objInt: + fprintf(f,"%i ", obj->getInt()); + break; + case objReal: + fprintf(f,"%f ", obj->getReal()); + break; + case objString: + writeString(obj->getString(), f); + break; + case objName: + fprintf(f,"/%s ", obj->getName()); + break; + case objNull: + fprintf(f, "null\r\n"); + break; + case objArray: + array = obj->getArray(); + fprintf(f,"["); + for (int i=0; igetLength(); i++) { + writeObject(array->getNF(i, &obj1), NULL,f); + obj1.free(); + } + fprintf(f,"]"); + break; + case objDict: + writeDictionnary (obj->getDict(),f); + break; + case objStream: + //we write the stream unencoded => TODO: write stream encoder + obj->getStream()->reset(); + //recalculate stream length + tmp = 0; + for (int c=obj->getStream()->getChar(); c!=EOF; c=obj->getStream()->getChar()) { + tmp++; + } + obj1.initInt(tmp); + obj->getStream()->getDict()->set("Length", &obj1); + + //Remove Stream encoding + obj->getStream()->getDict()->remove("Filter"); + obj->getStream()->getDict()->remove("DecodeParms"); + + writeDictionnary (obj->getStream()->getDict(),f); + writeStream (obj->getStream(),f); + obj1.free(); + break; + case objRef: + fprintf(f,"%i %i R ", obj->getRef().num, obj->getRef().gen); + break; + case objCmd: + printf("objCmd\n"); + fprintf(f,"null\r\n"); + break; + case objError: + printf("objError\n"); + fprintf(f,"null\r\n"); + break; + case objEOF: + printf("objEOF\n"); + fprintf(f,"null\r\n"); + break; + case objNone: + printf("objNone\n"); + fprintf(f,"null\r\n"); + break; + default: + error(-1,"Unhandled objType : %i, please report a bug with a testcase\r\n", obj->getType()); + break; + } + if (ref) + fprintf(f,"endobj\r\n\r\n"); + return offset; +} + +void PDFDoc::writeTrailer (Guint uxrefOffset, int uxrefSize, FILE* f, GBool incrUpdate) +{ + //Dict* trailerDict = xref->getTrailerDict()->getDict(); + Dict *trailerDict = new Dict(xref); + Object obj1; + obj1.initInt(uxrefSize); + trailerDict->set("Size", &obj1); + obj1.free(); + if(xref->getTrailerDict()->getDict()->lookup("ID", &obj1) != NULL) { + trailerDict->set("ID", &obj1); + obj1.free(); + } + obj1.initRef(xref->getRootNum(), xref->getRootGen()); + trailerDict->set("Root", &obj1); + obj1.free(); + + if (incrUpdate) { + obj1.initInt(xref->getLastXRefPos()); + trailerDict->set("Prev", &obj1); + obj1.free(); + } + fprintf(f, "trailer\r\n"); + writeDictionnary(trailerDict, f); + fprintf(f, "startxref\r\n"); + fprintf(f, "%i\r\n", uxrefOffset); + fprintf(f, "%%%%EOF\r\n"); +} + void PDFDoc::getLinks(Page *page) { Object obj; links = new Links(page->getAnnots(&obj), catalog->getBaseURI()); obj.free(); } Index: poppler/PDFDoc.h =================================================================== RCS file: /cvs/poppler/poppler/poppler/PDFDoc.h,v retrieving revision 1.7 diff -u -8 -p -B -r1.7 PDFDoc.h --- poppler/PDFDoc.h 18 Jan 2006 22:32:13 -0000 1.7 +++ poppler/PDFDoc.h 28 Jan 2007 21:06:58 -0000 @@ -22,16 +22,22 @@ class GooString; class BaseStream; class OutputDev; class Links; class LinkAction; class LinkDest; class Outline; +enum PDFWriteMode { + writeStandard, + writeForceRewrite, + writeForceIncremental +}; + //------------------------------------------------------------------------ // PDFDoc //------------------------------------------------------------------------ class PDFDoc { public: PDFDoc(GooString *fileNameA, GooString *ownerPassword = NULL, @@ -158,23 +164,31 @@ public: // Return the document's Info dictionary (if any). Object *getDocInfo(Object *obj) { return xref->getDocInfo(obj); } Object *getDocInfoNF(Object *obj) { return xref->getDocInfoNF(obj); } // Return the PDF version specified by the file. double getPDFVersion() { return pdfVersion; } - // Save this file with another name. - GBool saveAs(GooString *name); + // Save this file with another name + GBool saveAs(GooString *name, PDFWriteMode mode=writeStandard); // Return a pointer to the GUI (XPDFCore or WinPDFCore object). void *getGUIData() { return guiData; } private: + // Add object to current file stream and return the offset of the beginning of the object + Guint writeObject (Object *obj, Ref *ref, FILE* f); + void writeDictionnary (Dict* dict, FILE* f); + void writeStream (Stream* str, FILE* f); + void writeTrailer (Guint uxrefOffset, int uxrefSize, FILE* f, GBool incrUpdate); + void writeString (GooString* s, FILE* f); + void saveIncrementalUpdate (FILE* f); + void saveCompleteRewrite (FILE* f); GBool setup(GooString *ownerPassword, GooString *userPassword); GBool checkFooter(); void checkHeader(); GBool checkEncryption(GooString *ownerPassword, GooString *userPassword); void getLinks(Page *page); GooString *fileName; Index: poppler/Page.cc =================================================================== RCS file: /cvs/poppler/poppler/poppler/Page.cc,v retrieving revision 1.15 diff -u -8 -p -B -r1.15 Page.cc --- poppler/Page.cc 26 Dec 2006 19:56:29 -0000 1.15 +++ poppler/Page.cc 28 Jan 2007 21:06:59 -0000 @@ -21,20 +21,22 @@ #include "XRef.h" #include "Link.h" #include "OutputDev.h" #ifndef PDF_PARSER_ONLY #include "Gfx.h" #include "GfxState.h" #include "Annot.h" #include "TextOutputDev.h" +#include "Form.h" #endif #include "Error.h" #include "Page.h" #include "UGooString.h" +#include "Form.h" //------------------------------------------------------------------------ // PageAttrs //------------------------------------------------------------------------ PageAttrs::PageAttrs(PageAttrs *attrs, Dict *dict) { Object obj1; @@ -183,17 +185,17 @@ GBool PageAttrs::readBox(Dict *dict, cha obj1.free(); return ok; } //------------------------------------------------------------------------ // Page //------------------------------------------------------------------------ -Page::Page(XRef *xrefA, int numA, Dict *pageDict, PageAttrs *attrsA) { +Page::Page(XRef *xrefA, int numA, Dict *pageDict, PageAttrs *attrsA, Form *form) { Object tmp; ok = gTrue; xref = xrefA; num = numA; duration = -1; // get attributes @@ -221,16 +223,22 @@ Page::Page(XRef *xrefA, int numA, Dict * pageDict->lookupNF("Annots", &annots); if (!(annots.isRef() || annots.isArray() || annots.isNull())) { error(-1, "Page annotations object (page %d) is wrong type (%s)", num, annots.getTypeName()); annots.free(); goto err2; } + // forms + printf("--------- Loading FORMS for Page %i -------\n", numA); + pageWidgets = new FormPageWidgets(xrefA, this->getAnnots(&tmp),num,form); + tmp.free(); + printf("--------- done loading FORMS\n"); + // contents pageDict->lookupNF("Contents", &contents); if (!(contents.isRef() || contents.isArray() || contents.isNull())) { error(-1, "Page contents object (page %d) is wrong type (%s)", num, contents.getTypeName()); contents.free(); goto err1; @@ -415,19 +423,20 @@ void Page::displaySlice(OutputDev *out, if (annotList->getNumAnnots() > 0) { if (globalParams->getPrintCommands()) { printf("***** Annotations\n"); } for (i = 0; i < annotList->getNumAnnots(); ++i) { Annot *annot = annotList->getAnnot(i); if ((annotDisplayDecideCbk && (*annotDisplayDecideCbk)(annot, annotDisplayDecideCbkData)) || - !annotDisplayDecideCbk) + !annotDisplayDecideCbk) { annot->draw(gfx); } + } out->dump(); } delete annotList; delete gfx; } void Page::display(Gfx *gfx) { Index: poppler/Page.h =================================================================== RCS file: /cvs/poppler/poppler/poppler/Page.h,v retrieving revision 1.7 diff -u -8 -p -B -r1.7 Page.h --- poppler/Page.h 26 Dec 2006 19:56:29 -0000 1.7 +++ poppler/Page.h 28 Jan 2007 21:07:00 -0000 @@ -18,16 +18,18 @@ class Dict; class XRef; class OutputDev; class Links; class Catalog; class Annots; class Annot; class Gfx; +class FormPageWidgets; +class Form; //------------------------------------------------------------------------ class PDFRectangle { public: double x1, y1, x2, y2; PDFRectangle() { x1 = y1 = x2 = y2 = 0; } @@ -99,17 +101,17 @@ private: //------------------------------------------------------------------------ // Page //------------------------------------------------------------------------ class Page { public: // Constructor. - Page(XRef *xrefA, int numA, Dict *pageDict, PageAttrs *attrsA); + Page(XRef *xrefA, int numA, Dict *pageDict, PageAttrs *attrsA, Form *form); // Destructor. ~Page(); // Is page valid? GBool isOk() { return ok; } // Get page parameters. @@ -146,16 +148,19 @@ public: // Get thumb. Object *getThumb(Object *obj) { return thumb.fetch(xref, obj); } GBool loadThumb(unsigned char **data, int *width, int *height, int *rowstride); // Get transition. Object *getTrans(Object *obj) { return trans.fetch(xref, obj); } + // Get form. + FormPageWidgets *getPageWidgets() { return pageWidgets; } + // Get duration, the maximum length of time, in seconds, // that the page is displayed before the presentation automatically // advances to the next page double getDuration() { return duration; } // Get actions Object *getActions(Object *obj) { return actions.fetch(xref, obj); } @@ -195,16 +200,17 @@ public: private: XRef *xref; // the xref table for this PDF file int num; // page number PageAttrs *attrs; // page attributes Object annots; // annotations array Object contents; // page contents + FormPageWidgets *pageWidgets; // the form for that page Object thumb; // page thumbnail Object trans; // page transition Object actions; // page addiction actions double duration; // page duration GBool ok; // true if page is valid }; #endif Index: poppler/XRef.cc =================================================================== RCS file: /cvs/poppler/poppler/poppler/XRef.cc,v retrieving revision 1.13 diff -u -8 -p -B -r1.13 XRef.cc --- poppler/XRef.cc 1 Apr 2006 11:25:57 -0000 1.13 +++ poppler/XRef.cc 28 Jan 2007 21:07:01 -0000 @@ -196,16 +196,26 @@ Object *ObjectStream::getObject(int objI } return objs[objIdx].copy(obj); } //------------------------------------------------------------------------ // XRef //------------------------------------------------------------------------ +XRef::XRef() { + ok = gTrue; + errCode = errNone; + entries = NULL; + size = 0; + streamEnds = NULL; + streamEndsLen = 0; + objStr = NULL; +} + XRef::XRef(BaseStream *strA) { Guint pos; Object obj; ok = gTrue; errCode = errNone; size = 0; entries = NULL; @@ -259,17 +269,22 @@ XRef::XRef(BaseStream *strA) { } // now set the trailer dictionary's xref pointer so we can fetch // indirect objects from it trailerDict.getDict()->setXRef(this); } XRef::~XRef() { + for(int i=0; igetObj(&obj)->isInt()) { goto err1; } entry.offset = (Guint)obj.getInt(); obj.free(); if (!parser->getObj(&obj)->isInt()) { goto err1; } entry.gen = obj.getInt(); + entry.obj = NULL; obj.free(); parser->getObj(&obj); if (obj.isCmd("n")) { entry.type = xrefEntryUncompressed; } else if (obj.isCmd("f")) { entry.type = xrefEntryFree; } else { goto err1; @@ -502,16 +519,17 @@ GBool XRef::readXRefStream(Stream *xrefS if (newSize * (int)sizeof(XRefEntry)/sizeof(XRefEntry) != newSize) { error(-1, "Invalid 'size' parameter."); return gFalse; } entries = (XRefEntry *)greallocn(entries, newSize, sizeof(XRefEntry)); for (i = size; i < newSize; ++i) { entries[i].offset = 0xffffffff; entries[i].type = xrefEntryFree; + entries[i].obj = NULL; } size = newSize; } if (!dict->lookupNF("W", &obj)->isArray() || obj.arrayGetLength() < 3) { goto err1; } @@ -596,16 +614,17 @@ GBool XRef::readXRefStreamSection(Stream if (newSize*(int)sizeof(XRefEntry)/sizeof(XRefEntry) != newSize) { error(-1, "Invalid 'size' inside xref table."); return gFalse; } entries = (XRefEntry *)greallocn(entries, newSize, sizeof(XRefEntry)); for (i = size; i < newSize; ++i) { entries[i].offset = 0xffffffff; entries[i].type = xrefEntryFree; + entries[i].obj = NULL; } size = newSize; } for (i = first; i < first + n; ++i) { if (w[0] == 0) { type = 1; } else { for (type = 0, j = 0; j < w[0]; ++j) { @@ -736,16 +755,17 @@ GBool XRef::constructXRef() { error(-1, "Invalid 'obj' parameters."); return gFalse; } entries = (XRefEntry *) greallocn(entries, newSize, sizeof(XRefEntry)); for (i = size; i < newSize; ++i) { entries[i].offset = 0xffffffff; entries[i].type = xrefEntryFree; + entries[i].obj = NULL; } size = newSize; } if (entries[num].type == xrefEntryFree || gen >= entries[num].gen) { entries[num].offset = pos - start; entries[num].gen = gen; entries[num].type = xrefEntryUncompressed; @@ -845,16 +865,20 @@ Object *XRef::fetch(int num, int gen, Ob Object obj1, obj2, obj3; // check for bogus ref - this can happen in corrupted PDF files if (num < 0 || num >= size) { goto err; } e = &entries[num]; + if(e->obj) { //check for updated object + obj = e->obj->copy(obj); + return obj; + } switch (e->type) { case xrefEntryUncompressed: if (e->gen != gen) { goto err; } obj1.initNull(); parser = new Parser(this, @@ -962,8 +986,71 @@ Guint XRef::strToUnsigned(char *s) { int i; x = 0; for (p = s, i = 0; *p && isdigit(*p) && i < 10; ++p, ++i) { x = 10 * x + (*p - '0'); } return x; } + +void XRef::add(int num, int gen, Guint offs, GBool used) { + size += 1; + entries = (XRefEntry *)greallocn(entries, size, sizeof(XRefEntry)); + XRefEntry *e = &entries[size-1]; + + e->gen = gen; + e->num = num; + e->obj = NULL; + if (used) { + e->type = xrefEntryUncompressed; + e->offset = offs; + } else { + e->type = xrefEntryFree; + e->offset = 0; + } +} + +void XRef::setModifiedObject (Object* o, Ref r) { + if (r.num < 0 || r.num >= size) { + error(-1,"XRef::setModifiedObject on unknown ref: %i, %i\n", r.num, r.gen); + return; + } + entries[r.num].obj = new Object(); + o->copy(entries[r.num].obj); +} + +//used to sort the entries +int compare (const void* a, const void* b) +{ + return (((XRefEntry*)a)->num - ((XRefEntry*)b)->num); +} + +void XRef::writeToFile(FILE* file) { + qsort(entries, size, sizeof(XRefEntry), compare); + //create free entries linked-list + if (entries[0].gen != 65535) { + error(-1, "XRef::writeToFile, entry 0 of the XRef is invalid (gen != 65535)\n"); + } + int lastFreeEntry = 0; + for (int i=0; i 65535) entries[k].gen = 65535; //cap generation number to 65535 (required by PDFReference) + fprintf(file,"%010i %05i %c\r\n", entries[k].offset, entries[k].gen, (entries[k].type==xrefEntryFree)?'f':'n'); + } + i = j-1; + } +} + Index: poppler/XRef.h =================================================================== RCS file: /cvs/poppler/poppler/poppler/XRef.h,v retrieving revision 1.6 diff -u -8 -p -B -r1.6 XRef.h --- poppler/XRef.h 17 Jan 2006 21:35:31 -0000 1.6 +++ poppler/XRef.h 28 Jan 2007 21:07:02 -0000 @@ -29,22 +29,26 @@ enum XRefEntryType { xrefEntryFree, xrefEntryUncompressed, xrefEntryCompressed }; struct XRefEntry { Guint offset; int gen; + int num; XRefEntryType type; + Object* obj; }; class XRef { public: + // Constructor, create an empty XRef, used for PDF writing + XRef(); // Constructor. Read xref table from stream. XRef(BaseStream *strA); // Destructor. ~XRef(); // Is xref table valid? GBool isOk() { return ok; } @@ -97,16 +101,21 @@ public: // Retuns the entry that belongs to the offset int getNumEntry(int offset) const; // Direct access. int getSize() { return size; } XRefEntry *getEntry(int i) { return &entries[i]; } Object *getTrailerDict() { return &trailerDict; } + // Write access + void setModifiedObject(Object* o, Ref r); + void add(int num, int gen, Guint offs, GBool used); + void writeToFile(FILE* f); + private: BaseStream *str; // input stream Guint start; // offset in file (to allow for garbage // at beginning of file) XRefEntry *entries; // xref entries int size; // size of array int rootNum, rootGen; // catalog dict Index: test/Makefile.am =================================================================== RCS file: /cvs/poppler/poppler/test/Makefile.am,v retrieving revision 1.8 diff -u -8 -p -B -r1.8 Makefile.am --- test/Makefile.am 25 Sep 2006 20:43:18 -0000 1.8 +++ test/Makefile.am 28 Jan 2007 21:07:02 -0000 @@ -21,27 +21,31 @@ pdf_inspector = \ cairo_includes = \ $(CAIRO_CFLAGS) \ $(FREETYPE_CFLAGS) endif endif +pdf_modification_test = \ + pdf-modification-test + + INCLUDES = \ -I$(top_srcdir) \ -I$(top_srcdir)/poppler \ -I$(top_srcdir)/glib \ -I$(top_builddir)/glib \ $(cairo_includes) \ $(GTK_TEST_CFLAGS) \ $(FONTCONFIG_CFLAGS) -noinst_PROGRAMS = $(gtk_splash_test) $(gtk_cairo_test) $(pdf_inspector) +noinst_PROGRAMS = $(gtk_splash_test) $(gtk_cairo_test) $(pdf_inspector) $(pdf_modification_test) gtk_splash_test_SOURCES = \ gtk-splash-test.cc gtk_splash_test_LDADD = \ $(top_builddir)/poppler/libpoppler.la \ $(GTK_TEST_LIBS) @@ -60,10 +64,17 @@ pdf_inspector_SOURCES = \ pdf_inspector_LDADD = \ $(top_builddir)/poppler/libpoppler.la \ $(top_builddir)/poppler/libpoppler-cairo.la \ $(CAIRO_LIBS) \ $(FREETYPE_LIBS) \ $(GTK_TEST_LIBS) +pdf_modification_test_SOURCES = \ + pdf-modification-test.cc + +pdf_modification_test_LDADD = \ + $(top_builddir)/poppler/libpoppler.la + + EXTRA_DIST = \ pdf-operators.c Index: test/pdf-modification-test.cc =================================================================== RCS file: test/pdf-modification-test.cc diff -N test/pdf-modification-test.cc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ test/pdf-modification-test.cc 28 Jan 2007 21:07:02 -0000 @@ -0,0 +1,84 @@ +//======================================================================== +// +// pdftotext.cc +// +// Copyright 2006 Julien Rebetez +// +//======================================================================== + +#include "config.h" +#include +#include +#include +#include "goo/GooString.h" +#include "goo/gmem.h" +#include "GlobalParams.h" +#include "Object.h" +#include "Stream.h" +#include "Array.h" +#include "Dict.h" +#include "XRef.h" +#include "Catalog.h" +#include "Page.h" +#include "PDFDoc.h" +#include "TextOutputDev.h" +#include "CharTypes.h" +#include "UnicodeMap.h" +#include "Error.h" +#include "UGooString.h" + +void modifyCatalogLang(PDFDoc *doc, GooString* lang); + +int main(int argc, char **argv) +{ + PDFDoc *doc; + GBool ok; + GooString *inputFileName; + GooString *newLang; + GooString *outputs[3]; + + // parse args + if (argc < 3 || argc > 4) { + fprintf(stderr, "pdf-modification-test\n"); + printf("usage: \n"); + return 0; + } + + inputFileName = new GooString(argv[1]); + newLang = new GooString(argv[3]); + outputs[0] = new GooString(argv[2]); outputs[0]->append("/no_update.pdf"); + outputs[1] = new GooString(argv[2]); outputs[1]->append("/update_full_rewrite.pdf"); + outputs[2] = new GooString(argv[2]); outputs[2]->append("/update_incremental.pdf"); + doc = new PDFDoc(inputFileName, NULL, NULL); + doc->saveAs(outputs[0], writeForceRewrite); + modifyCatalogLang(doc, newLang); + doc->saveAs(outputs[1], writeForceRewrite); + doc->saveAs(outputs[2], writeForceIncremental); + + delete newLang; + for(int i=0; i<3; i++) delete outputs[i]; + delete doc; +} + +void modifyCatalogLang(PDFDoc* doc, GooString* lang) +{ + Object *catalog = new Object(); + XRef *xref = doc->getXRef(); + catalog = xref->getCatalog(catalog); + + Object o; + catalog->dictLookup("Lang", &o); + if(o.isString()) printf("input Lang: %s\n", o.getString()->getCString()); + if(catalog->isDict()) { + Object* val = new Object(); + val->initString(lang); + printf("output Lang: %s\n", val->getString()->getCString()); + catalog->dictSet("Lang", val); + } + Ref newRef; + newRef.num = xref->getRootNum(); + newRef.gen = xref->getRootGen(); + xref->setModifiedObject(catalog, newRef); +} + +