Index: xpdf/PDFCore.h =================================================================== --- xpdf/PDFCore.h (revision 1) +++ xpdf/PDFCore.h (working copy) @@ -200,6 +200,10 @@ GBool getSelection(int *pg, double *ulx, double *uly, double *lrx, double *lry); + // Note drawing + GBool finalizeDraw(); + void drawPoint(int pg, int x, int y); + // Text extraction. GString *extractText(int pg, double xMin, double yMin, double xMax, double yMax); @@ -231,6 +235,10 @@ int getPageNum() { return topPage; } double getZoom() { return zoom; } double getZoomDPI() { return dpi; } + void setColor(Guchar R, Guchar G, Guchar B) { + color[0] = R; color[1] = G; color[2] = B; + } + void setThickness(int value) { thickness = value; } int getRotate() { return rotate; } GBool getContinuousMode() { return continuousMode; } virtual void setReverseVideo(GBool reverseVideoA); @@ -242,6 +250,10 @@ int getDrawAreaHeight() { return drawAreaHeight; } virtual void setBusyCursor(GBool busy) = 0; LinkAction *findLink(int pg, double x, double y); + GBool getModified() { return modified; } + void setModified(GBool mod) { modified = mod; } + GBool getTemporary() { return temporary; } + void setTemporary(GBool mod) { temporary = mod; } protected: @@ -271,6 +283,9 @@ virtual GBool checkForNewFile() { return gFalse; } PDFDoc *doc; // current PDF file + GBool modified; // set if the document need to be saved + // before quitting + GBool temporary; // set if the current document is temporary GBool continuousMode; // false for single-page mode, true for // continuous mode int drawAreaWidth, // size of the PDF display area @@ -290,11 +305,29 @@ double dpi; // current zoom level, in DPI int rotate; // current page rotation + Guchar color[3]; // current color used for drawing annotations + int thickness; // current thickness used for drawing annotations + int selectPage; // page number of current selection int selectULX, // coordinates of current selection, selectULY, // in device space -- (ULX==LRX || ULY==LRY) selectLRX, // means there is no selection selectLRY; + + GBool drawing; // set while drawing is happening; everytime we release + // the mouse button, drawing is reset + GBool drawn; // used to check if we have modified the file + int lastDrawnX, // coordinates of the last drawed point: used for rendering + lastDrawnY; // on the screen + double lastStreamX, // coordinates of the last stored point inside the + lastStreamY; // temporary file of the annotation stream + int numDrawnPoints; // counter for the points added to the temp stream; + + double rectXmin, // coordinates of the bounding box for the annotation + rectYmin, // we are drawing at execution time + rectXmax, + rectYmax; + GBool dragging; // set while selection is being dragged GBool lastDragLeft; // last dragged selection edge was left/right GBool lastDragTop; // last dragged selection edge was top/bottom Index: xpdf/XRef.h =================================================================== --- xpdf/XRef.h (revision 1) +++ xpdf/XRef.h (working copy) @@ -79,7 +79,7 @@ Object *getDocInfoNF(Object *obj); // Return the number of objects in the xref table. - int getNumObjects() { return size; } + int getNumObjects() { return numObjects; } // Return the offset of the last xref table. Guint getLastXRefPos() { return lastXRefPos; } @@ -104,6 +104,7 @@ // at beginning of file) XRefEntry *entries; // xref entries int size; // size of array + int numObjects; //number of objects inside entries array int rootNum, rootGen; // catalog dict GBool ok; // true if xref table is valid int errCode; // error code (if is false) Index: xpdf/Page.h =================================================================== --- xpdf/Page.h (revision 1) +++ xpdf/Page.h (working copy) @@ -16,6 +16,8 @@ #endif #include "Object.h" +#include "GfxState.h" +#include "Annot.h" class Dict; class XRef; @@ -142,6 +144,12 @@ // Get annotations array. Object *getAnnots(Object *obj) { return annots.fetch(xref, obj); } + // Get annotations information + int getAnnotNum() { return annotNum; } + int getAnnotGen() { return annotGen; } + int getSelectedAnnotation () { return selectedAnnotation; } + void setSelectedAnnotation (int i) { selectedAnnotation = i; } + // Return a list of links. Links *getLinks(Catalog *catalog); @@ -180,6 +188,9 @@ int num; // page number PageAttrs *attrs; // page attributes Object annots; // annotations array + int annotNum, // annotation reference number + annotGen, // annotation generation number + selectedAnnotation; // index of selected annotation Object contents; // page contents GBool ok; // true if page is valid }; Index: xpdf/XRef.cc =================================================================== --- xpdf/XRef.cc (revision 1) +++ xpdf/XRef.cc (working copy) @@ -197,6 +197,7 @@ ok = gTrue; errCode = errNone; size = 0; + numObjects = 0; entries = NULL; streamEnds = NULL; streamEndsLen = 0; @@ -367,6 +368,7 @@ goto err1; } n = obj.getInt(); + numObjects += n; obj.free(); if (first < 0 || n < 0 || first + n < 0) { goto err1; @@ -374,7 +376,7 @@ if (first + n > size) { for (newSize = size ? 2 * size : 1024; first + n > newSize && newSize > 0; - newSize <<= 1) ; + newSize <<= 1) if (newSize < 0) { goto err1; } @@ -426,7 +428,7 @@ goto err1; } - // get the 'Prev' pointer + // get the 'Prev' pointer(puntatore alla Xref precedente) obj.getDict()->lookupNF("Prev", &obj2); if (obj2.isInt()) { *pos = (Guint)obj2.getInt(); @@ -489,6 +491,7 @@ entries[i].offset = 0xffffffff; entries[i].type = xrefEntryFree; } + numObjects += newSize - size; size = newSize; } @@ -580,6 +583,7 @@ entries[i].type = xrefEntryFree; } size = newSize; + numObjects += n; } for (i = first; i < first + n; ++i) { if (w[0] == 0) { @@ -645,6 +649,7 @@ gfree(entries); size = 0; + numObjects = 0; entries = NULL; error(-1, "PDF file is damaged - attempting to reconstruct xref table..."); @@ -743,6 +748,7 @@ } } + numObjects = num; if (gotRoot) return gTrue; Index: xpdf/XPDFCore.cc =================================================================== --- xpdf/XPDFCore.cc (revision 1) +++ xpdf/XPDFCore.cc (working copy) @@ -29,6 +29,7 @@ #include "TextOutputDev.h" #include "SplashBitmap.h" #include "SplashPattern.h" +#include "Annot.h" #include "XPDFApp.h" #include "XPDFCore.h" @@ -140,6 +141,8 @@ hyperlinksEnabled = gTrue; selectEnabled = gTrue; + drawEnabled = gFalse; + // do X-specific initialization and create the widgets initWindow(); initPasswordDialog(); @@ -166,7 +169,10 @@ if (selectCursor) { XFreeCursor(display, selectCursor); } + if (drawCursor) { + XFreeCursor(display, drawCursor); } +} //------------------------------------------------------------------------ // loadFile / displayPage / displayDest @@ -356,11 +362,44 @@ void XPDFCore::startSelection(int wx, int wy) { int pg, x, y; + int i, j, length, inkListLen; + double Xmin, Ymin, Xmax, Ymax, ux, uy; + int dxmin, dymin, dxmax, dymax; + Page *page; + Annots *annotList; + Annot *annot; + Object obj; takeFocus(); if (doc && doc->getNumPages() > 0) { - if (selectEnabled) { if (cvtWindowToDev(wx, wy, &pg, &x, &y)) { + if (selectEnabled && !drawEnabled) { + // this block is used to check if we are trying + // to select an annotation + XRef *xref = doc->getXRef(); + Catalog *cat = doc->getCatalog(); + Ref *ref = cat->getPageRef(pg); + XRefEntry *xrefEntry = xref->getEntry(ref->num); + page = cat->getPage(pg); + annotList = new Annots(xref, cat, page->getAnnots(&obj)); + obj.free(); + length = annotList->getNumAnnots(); + for (i = 0; i < annotList->getNumAnnots(); ++i) { + annot = annotList->getAnnot(i); + annot->getRectangle(&Xmin, &Ymin, &Xmax, &Ymax); + cvtUserToDev(pg, Xmin, Ymin, &dxmin, &dymax); + cvtUserToDev(pg, Xmax, Ymax, &dxmax, &dymin); + cvtWindowToUser(wx, wy, &pg, &ux, &uy); + if (annot->selectAnnotation(ux, uy)) { + page->setSelectedAnnotation(i); + setSelection(pg, dxmin, dymin, dxmax, dymax); + delete annotList; + return; + } + } + delete annotList; + // otherwise, we start a selection + page->setSelectedAnnotation(-1); setSelection(pg, x, y, x, y); setCursor(selectCursor); dragging = gTrue; @@ -379,11 +418,11 @@ dragging = gFalse; setCursor(None); if (ok) { + if (selectEnabled && !drawEnabled) moveSelection(pg, x, y); } #ifndef NO_TEXT_SELECT - if (selectULX != selectLRX && - selectULY != selectLRY) { + if (selectULX != selectLRX && selectULY != selectLRY) { if (doc->okToCopy()) { copySelection(); } else { @@ -454,6 +493,158 @@ } //------------------------------------------------------------------------ +// annotations +//------------------------------------------------------------------------ + +// called when pressing the 'draw' button, creates the header for +// the annotation and the stream. +void XPDFCore::initializeDraw(int pg, int x, int y) { + double dx, dy; + double r, g, b; + + GString **ann = doc->getAnnotationObject(); + if (*ann) // annotation already started. + return; + r = 1.0*(Guint)color[0]/(Guint)0xff; + g = 1.0*(Guint)color[1]/(Guint)0xff; + b = 1.0*(Guint)color[2]/(Guint)0xff; + + cvtDevToUser(pg, x, y, &dx, &dy); + *ann = GString::format( + "<<\n"\ + "/Type/Annot\n"\ + "/Subtype/Ink\n"\ + "/C[{0:.1f} {1:.1f} {2:.1f}]\n"\ + "/Subject(Pencil)\n"\ + "/BS <>\n"\ + "/InkList[", r, g, b, thickness); + + *doc->getAppereanceStream() = GString::format( + "{0:.1f} {1:.1f} {2:.1f} RG\n{3:d} w\n", r, g, b, thickness); + + numDrawnPoints = 0; + rectXmin = rectXmax = dx; + rectYmin = rectYmax = dy; +} + +// the actual callback on a start Draw +void XPDFCore::startDraw(int wx, int wy) { + int pg, x, y; + + takeFocus(); + if (doc && doc->getNumPages() > 0) { + if (drawEnabled) { + if (cvtWindowToDev(wx, wy, &pg, &x, &y)) { + if (!drawn) { + initializeDraw(pg, x, y); + drawn = gTrue; + } + continueDraw(pg, x, y, 0); + drawing = gTrue; + drawPoint(pg, x, y); + setCursor(drawCursor); + } + } + } +} + +// flag=0 on the first point, 1 on intermediate, 2 on the last one. +// add an element to the annotation and also to the stream +void XPDFCore::continueDraw(int pg, int x, int y, int flag) { + double dx, dy; + GString *s = *doc->getAnnotationObject(); + + cvtDevToUser(pg, x, y, &dx, &dy); + if (flag == 0) + s->append("["); + s->appendf("{0:.1f} {1:.1f} ", dx, dy); + if (flag == 2) + s->append("]"); + + s = *doc->getAppereanceStream(); + if (flag == 0) { // first point, store unconditionally + s->appendf("{0:.1f} {1:.1f} m\n", dx, dy); + numDrawnPoints++; + lastStreamX = dx; + lastStreamY = dy; + } else if (flag == 1) { // other point + // compute distance, ignore if too close + double delta = (lastStreamX - dx)*(lastStreamX - dx) + + (lastStreamY - dy)*(lastStreamY - dy); + if (delta > 49) { + s->appendf("{0:.1f} {1:.1f} ", dx, dy); + numDrawnPoints++; + if (numDrawnPoints%3 == 1) { + s->append("c\n"); + } + lastStreamX = dx; + lastStreamY = dy; + } + } else { // XXX final point. We do not plot the point itself, + // and close the bezier curve in a sensible way depending on + //the number of points previously printed. + if (numDrawnPoints%3 == 2) + s->append("l\nS\n"); + else if (numDrawnPoints%3 == 0) + s->append("v\nS\n"); + else + s->append("S\n"); + numDrawnPoints = 0; + } + + // update the coordinates of the bounding box + if (dx < rectXmin) + rectXmin = dx; + if (dx > rectXmax) + rectXmax = dx; + if (dy < rectYmin) + rectYmin = dy; + if (dy > rectYmax) + rectYmax = dy; +} + +// should be similar to end selection +void XPDFCore::endDraw(int wx, int wy) { + int pg, x, y; + GBool ok; + + ok = cvtWindowToDev(wx, wy, &pg, &x, &y); + if (drawEnabled) { + if (!ok) { // outside the window, reuse the last point + x = lastDrawnX; + y = lastDrawnY; + } + continueDraw(pg, x, y, 2); + drawPoint(pg, x, y); + lastDrawnX = lastDrawnY = -1; + drawing = gFalse; + setCursor(None); + } +} + +// handler when deleting an annotation: make a temp copy, +// overwrite the object with whitespace, and reload. +void XPDFCore::deleteAnnotation() { + Catalog *cat = doc->getCatalog(); + if (!cat) + return; + + Page *page = cat->getPage(topPage); + if (!page || page->getSelectedAnnotation() == -1) + return; + + int pg = topPage; + GString *temp = doc->delTempAnnotation(pg, page->getSelectedAnnotation(), getTemporary()); + if (temp == NULL) + return; + setTemporary(gTrue); + setModified(gTrue); + loadFile(temp); + displayPage(pg, getZoom(), getRotate(), gFalse, gFalse); + return; +} + +//------------------------------------------------------------------------ // hyperlinks //------------------------------------------------------------------------ @@ -883,6 +1074,7 @@ busyCursor = XCreateFontCursor(display, XC_watch); linkCursor = XCreateFontCursor(display, XC_hand2); selectCursor = XCreateFontCursor(display, XC_cross); + drawCursor = XCreateFontCursor(display, XC_pencil); currentCursor = 0; // create the scrolled window and scrollbars @@ -1065,7 +1257,6 @@ char *s; KeySym key; GBool ok; - switch (data->event->type) { case ButtonPress: if (*core->mouseCbk) { @@ -1083,8 +1274,17 @@ &pg, &x, &y); if (core->dragging) { if (ok) { + if (core->selectEnabled) { core->moveSelection(pg, x, y); } + } + } else if (core->drawing) { + if (ok) { + if (core->drawEnabled) { + core->continueDraw(pg, x, y, 1); + core->drawPoint(pg, x, y); + } + } } else if (core->hyperlinksEnabled) { core->cvtDevToUser(pg, x, y, &xu, &yu); if (ok && (action = core->findLink(pg, xu, yu))) { @@ -1500,6 +1700,67 @@ core->dialogDone = -1; } +GBool XPDFCore::doPromptDialog(char *title, GString *msg) { + Widget dialog; + XtAppContext appContext; + Arg args[20]; + int n; + XmString s1, s2, s3; + XEvent event; + + n = 0; + XtSetArg(args[n], XmNdialogType, XmDIALOG_PROMPT); ++n; + XtSetArg(args[n], XmNdialogStyle, XmDIALOG_PRIMARY_APPLICATION_MODAL); ++n; + s1 = XmStringCreateLocalized(title); + XtSetArg(args[n], XmNdialogTitle, s1); ++n; + s2 = XmStringCreateLocalized(msg->getCString()); + XtSetArg(args[n], XmNselectionLabelString, s2); ++n; + s3 = XmStringCreateLocalized(""); + XtSetArg(args[n], XmNtextString, s3); ++n; + dialog = XmCreatePromptDialog(drawArea, "promptDialog", args, n); + XmStringFree(s1); + XmStringFree(s2); + XmStringFree(s3); + XtManageChild(dialog); + + XtAddCallback(dialog, XmNokCallback, + &dialogPromptOkCbk, (XtPointer)this); + XtAddCallback(dialog, XmNcancelCallback, + &dialogPromptCancelCbk, (XtPointer)this); + + appContext = XtWidgetToApplicationContext(dialog); + dialogDone = 0; + do { + XtAppNextEvent(appContext, &event); + XtDispatchEvent(&event); + } while (!dialogDone); + + XtUnmanageChild(dialog); + XtDestroyWidget(dialog); + + return dialogDone > 0; +} + +void XPDFCore::dialogPromptOkCbk(Widget widget, XtPointer ptr, + XtPointer callData) { + XPDFCore *core = (XPDFCore *)ptr; + XmSelectionBoxCallbackStruct *selection = + (XmSelectionBoxCallbackStruct *)callData; + char *str = (char *)XmStringUnparse(selection->value, 0, XmCHARSET_TEXT, XmCHARSET_TEXT, 0, 0, XmOUTPUT_ALL); + printf("%s\n", (str && str[0]) ? str : "(NULL)"); + XtFree(str); + core->dialogDone = 1; +} + +void XPDFCore::dialogPromptCancelCbk(Widget widget, XtPointer ptr, + XtPointer callData) { + XPDFCore *core = (XPDFCore *)ptr; + XmSelectionBoxCallbackStruct *selection = + (XmSelectionBoxCallbackStruct *)callData; + printf("(NULL)\n"); + core->dialogDone = -1; +} + //------------------------------------------------------------------------ // password dialog //------------------------------------------------------------------------ Index: xpdf/Page.cc =================================================================== --- xpdf/Page.cc (revision 1) +++ xpdf/Page.cc (working copy) @@ -221,6 +221,13 @@ annots.free(); goto err2; } + if (annots.isRef()) { + annotNum = annots.getRef().num; + annotGen = annots.getRef().gen; + } else { + annotNum = annotGen = -1; + } + selectedAnnotation = -1; // contents pageDict->lookupNF("Contents", &contents); @@ -261,6 +268,7 @@ GBool printing, Catalog *catalog, GBool (*abortCheckCbk)(void *data), void *abortCheckCbkData) { + setSelectedAnnotation(-1); displaySlice(out, hDPI, vDPI, rotate, useMediaBox, crop, -1, -1, -1, -1, printing, catalog, abortCheckCbk, abortCheckCbkData); Index: xpdf/PDFDoc.h =================================================================== --- xpdf/PDFDoc.h (revision 1) +++ xpdf/PDFDoc.h (working copy) @@ -154,10 +154,17 @@ // Save this file with another name. GBool saveAs(GString *name); + // Save file with added annotations + GString *addTempAnnotation(int pg, GBool temporary); + GString *delTempAnnotation(int pg, int n, GBool temporary); + // Misc access for temporary annotations + GString **getAnnotationObject() { return &annotationObject; } + GString **getAppereanceObject() { return &appereanceObject; } + GString **getAppereanceStream() { return &appereanceStream; } + // Return a pointer to the GUI (XPDFCore or WinPDFCore object). void *getGUIData() { return guiData; } - private: GBool setup(GString *ownerPassword, GString *userPassword); @@ -175,6 +182,9 @@ Outline *outline; #endif + GString *annotationObject, // local buffers used during creation of + *appereanceObject, // temporary annotations + *appereanceStream; GBool ok; int errCode; Index: xpdf/XPDFCore.h =================================================================== --- xpdf/XPDFCore.h (revision 1) +++ xpdf/XPDFCore.h (working copy) @@ -98,6 +98,14 @@ void startPan(int wx, int wy); void endPan(int wx, int wy); + //----- annotations + + void initializeDraw(int pg, int x, int y); + void startDraw(int wx, int wy); + void continueDraw(int pg, int x, int y, int flag); + void endDraw(int wx, int wy); + void deleteAnnotation(); + //----- hyperlinks void doAction(LinkAction *action); @@ -116,6 +124,7 @@ GBool doQuestionDialog(char *title, GString *msg); void doInfoDialog(char *title, GString *msg); void doErrorDialog(char *title, GString *msg); + GBool doPromptDialog(char *title, GString *msg); //----- password dialog @@ -128,9 +137,12 @@ virtual void setBusyCursor(GBool busy); Cursor getBusyCursor() { return busyCursor; } void takeFocus(); + void enableDraw(GBool on) { drawEnabled = on; } + GBool getDrawEnabled() { return drawEnabled; } void enableHyperlinks(GBool on) { hyperlinksEnabled = on; } GBool getHyperlinksEnabled() { return hyperlinksEnabled; } void enableSelect(GBool on) { selectEnabled = on; } + GBool getDrawn() { return drawn; } void setUpdateCbk(XPDFUpdateCbk cbk, void *data) { updateCbk = cbk; updateCbkData = data; } void setActionCbk(XPDFActionCbk cbk, void *data) @@ -189,6 +201,10 @@ XtPointer callData); static void passwordCancelCbk(Widget widget, XtPointer ptr, XtPointer callData); + static void dialogPromptOkCbk(Widget widget, XtPointer ptr, + XtPointer callData); + static void dialogPromptCancelCbk(Widget widget, XtPointer ptr, + XtPointer callData); Gulong paperPixel; Gulong mattePixel; @@ -214,7 +230,7 @@ Widget vScrollBar; Widget drawAreaFrame; Widget drawArea; - Cursor busyCursor, linkCursor, selectCursor; + Cursor busyCursor, linkCursor, selectCursor, drawCursor; Cursor currentCursor; GC drawAreaGC; // GC for blitting into drawArea @@ -238,6 +254,7 @@ XPDFMouseCbk mouseCbk; void *mouseCbkData; + GBool drawEnabled; //flag indicating whether we are drawing or not GBool hyperlinksEnabled; GBool selectEnabled; Index: xpdf/Annot.h =================================================================== --- xpdf/Annot.h (revision 1) +++ xpdf/Annot.h (working copy) @@ -72,6 +72,13 @@ // Get appearance object. Object *getAppearance(Object *obj) { return appearance.fetch(xref, obj); } + // Get annotation rectangle + void getRectangle(double *Xmin, double *Ymin, double *Xmax, double *Ymax) + { *Xmin = xMin; *Ymin = yMin; *Xmax = xMax; *Ymax = yMax; } + + // check if the given point is within the annotation rectangle + GBool selectAnnotation(double ux, double uy); + AnnotBorderStyle *getBorderStyle() { return borderStyle; } GBool match(Ref *refA) @@ -101,6 +108,7 @@ GString *type; // annotation type Object appearance; // a reference to the Form XObject stream // for the normal appearance + Object inkList; // a reference to the InkList array GString *appearBuf; double xMin, yMin, // annotation rectangle xMax, yMax; Index: xpdf/XPDFViewer.h =================================================================== --- xpdf/XPDFViewer.h (revision 1) +++ xpdf/XPDFViewer.h (working copy) @@ -40,8 +40,9 @@ //------------------------------------------------------------------------ -// NB: this must match the defn of zoomMenuBtnInfo in XPDFViewer.cc -#define nZoomMenuItems 10 +#define nZoomMenuItems sizeof(zoomMenuInfo)/sizeof(ZoomMenuInfo) +#define nThicknessMenuItems sizeof(thicknessMenuInfo)/sizeof(ThicknessMenuInfo) +#define nColorMenuItems sizeof(colorMenuInfo)/sizeof(ColorMenuInfo) //------------------------------------------------------------------------ @@ -104,6 +105,8 @@ void cmdCloseOutline(GString *args[], int nArgs, XEvent *event); void cmdCloseWindow(GString *args[], int nArgs, XEvent *event); void cmdContinuousMode(GString *args[], int nArgs, XEvent *event); + void cmdDeleteAnnotation(GString *args[], int nArgs, XEvent *event); + void cmdEndDraw(GString *args[], int nArgs, XEvent *event); void cmdEndPan(GString *args[], int nArgs, XEvent *event); void cmdEndSelection(GString *args[], int nArgs, XEvent *event); void cmdFind(GString *args[], int nArgs, XEvent *event); @@ -159,6 +162,7 @@ void cmdScrollUp(GString *args[], int nArgs, XEvent *event); void cmdScrollUpPrevPage(GString *args[], int nArgs, XEvent *event); void cmdSinglePageMode(GString *args[], int nArgs, XEvent *event); + void cmdStartDraw(GString *args[], int nArgs, XEvent *event); void cmdStartPan(GString *args[], int nArgs, XEvent *event); void cmdStartSelection(GString *args[], int nArgs, XEvent *event); void cmdToggleContinuousMode(GString *args[], int nArgs, XEvent *event); @@ -201,9 +205,17 @@ #if USE_COMBO_BOX static void zoomComboBoxCbk(Widget widget, XtPointer ptr, XtPointer callData); + static void thicknessComboBoxCbk(Widget widget, XtPointer ptr, + XtPointer callData); + static void colorComboBoxCbk(Widget widget, XtPointer ptr, + XtPointer callData); #else static void zoomMenuCbk(Widget widget, XtPointer ptr, XtPointer callData); + static void thicknessMenuCbk(Widget widget, XtPointer ptr, + XtPointer callData); + static void colorMenuCbk(Widget widget, XtPointer ptr, + XtPointer callData); #endif static void findCbk(Widget widget, XtPointer ptr, XtPointer callData); @@ -211,6 +223,8 @@ XtPointer callData); static void aboutCbk(Widget widget, XtPointer ptr, XtPointer callData); + static void drawCbk(Widget widget, XtPointer ptr, + XtPointer callData); static void quitCbk(Widget widget, XtPointer ptr, XtPointer callData); static void openCbk(Widget widget, XtPointer ptr, @@ -248,6 +262,8 @@ XtPointer callData); #endif + GBool save_helper(); // save temp file on close + //----- GUI code: "about" dialog void initAboutDialog(); @@ -315,14 +331,22 @@ Widget pageCountLabel; #if USE_COMBO_BOX Widget zoomComboBox; + Widget colorComboBox; + Widget thicknessComboBox; #else Widget zoomMenu; + Widget thicknessMenu; + Widget colorMenu; Widget zoomMenuBtns[nZoomMenuItems]; + Widget colorMenuBtns[nColorMenuItems]; #endif Widget zoomWidget; Widget findBtn; Widget printBtn; Widget aboutBtn; + Widget thicknessWidget; + Widget colorWidget; + Widget drawBtn; Widget linkLabel; Widget quitBtn; Widget popupMenu; Index: xpdf/PDFDoc.cc =================================================================== --- xpdf/PDFDoc.cc (revision 1) +++ xpdf/PDFDoc.cc (working copy) @@ -64,11 +64,11 @@ #ifndef DISABLE_OUTLINE outline = NULL; #endif + annotationObject = appereanceObject = appereanceStream = NULL; fileName = fileNameA; fileName1 = fileName; - // try to open file fileName2 = NULL; #ifdef VMS @@ -117,10 +117,12 @@ file = NULL; str = NULL; xref = NULL; + catalog = NULL; #ifndef DISABLE_OUTLINE outline = NULL; #endif + annotationObject = appereanceObject = appereanceStream = NULL; //~ file name should be stored in Unicode (?) fileName = new GString(); @@ -174,6 +176,7 @@ #ifndef DISABLE_OUTLINE outline = NULL; #endif + annotationObject = appereanceObject = appereanceStream = NULL; ok = setup(ownerPassword, userPassword); } @@ -400,3 +403,340 @@ fclose(f); return gTrue; } + +// copy data from str to tempFp. If len == -1 copy to the end +// of file, otherwise copy the specified number of bytes. +static int stream_copy(BaseStream *str, FILE *tempFp, int len) { + int i; + for (i = 0; len == -1 || i < len; ++i) { + int c = str->getChar(); + if (c == EOF) + break; + fputc(c, tempFp); + } + return i; // number of bytes written +} + +// make a copy of the stream into a temp file, +// increase len by the amount of bytes written +static FILE *make_temp_copy(GString *fileName, BaseStream *str, int *len) +{ + int dummy = 0; + if (len == NULL) + len = &dummy; + GString *tempFileName = fileName->copy()->append(".temp"); + FILE *tempFp = fopen(tempFileName->getCString(), "wb"); + delete tempFileName; + if (tempFp == NULL) /*ERROR*/ + return NULL; + str->reset(); + *len += stream_copy(str, tempFp, -1); // copy to EOF + str->close(); + return tempFp; +} + +// called when the user closes an annotation so it is saved. +// We need to make a temporary copy, and append the various pieces. +GString *PDFDoc::addTempAnnotation(int pg, GBool temporary) { + int written = 0, + offPage = 0, + offAnnArr = 0, + offAnn, offAp, + start, end, c, i, + numObjects = xref->getNumObjects(); + + // make a temporary copy of the whole file. + FILE *tempFp = make_temp_copy(fileName, str, &written); + if (tempFp == NULL) //ERROR + return NULL; + + Page *page = catalog->getPage(pg); + Object obj; + Annots* annotList = new Annots(xref, catalog, page->getAnnots(&obj)); + obj.free(); + Ref *ref = catalog->getPageRef(pg); + XRefEntry *pageRef = xref->getEntry(ref->num); + str->reset(); + Lexer *lexer = new Lexer(xref, str); + + if (annotList->getNumAnnots() == 0) { + // there are no annotations yet. + offPage = written; + lexer->setPos(pageRef->offset); + start = lexer->getPos(); + lexer->getObj(&obj); // get page number + obj.free(); + lexer->getObj(&obj); // get page gen + obj.free(); + lexer->getObj(&obj); // get "obj" + obj.free(); + lexer->getObj(&obj); // get begin of dictionary + obj.free(); + + end = lexer->getPos(); + lexer->setPos(start); + written += stream_copy(str, tempFp, end - start); + start = end; + written += fprintf(tempFp, " /Annots [ %d 0 R ]", numObjects); + } else { + // we do have an annotation, either object or inline. + // Locate the beginning of the array. + if (page->getAnnotNum() != -1) { // annotation object, jump there. + offAnnArr = written; + lexer->setPos(xref->getEntry(page->getAnnotNum())->offset); + start = lexer->getPos(); + lexer->getObj(&obj); // get annotation array number + obj.free(); + lexer->getObj(&obj); // get annotation array gen + obj.free(); + lexer->getObj(&obj); // get "obj" + obj.free(); + lexer->getObj(&obj); // get begin of array "[" + obj.free(); + } else { + // the annotation is inline. Again, move to the beginning of + // the object. + offPage = written; + lexer->setPos(pageRef->offset); + start = lexer->getPos(); + while (1) { + lexer->getObj(&obj); + if (obj.isName() && !strncmp("Annots", obj.getName(), 6)) { + obj.free(); + lexer->getObj(&obj); // begin of array "[" + obj.free(); + break; + } + obj.free(); + } + } + // common part: jump to the end of the array and + // add the annotation reference + for (i = 0; i < annotList->getNumAnnots(); ++i) { + lexer->getObj(&obj); // annotation number + obj.free(); + lexer->getObj(&obj); // annotation gen + obj.free(); + lexer->getObj(&obj); // "R" + obj.free(); + } + end = lexer->getPos(); + lexer->setPos(start); + written += stream_copy(str, tempFp, end - start); + start = end; + written += fprintf(tempFp, " %d 0 R ", numObjects); + } + // copy till "endobj" + while (1) { + lexer->getObj(&obj); + if (obj.isCmd() && !strncmp("endobj", obj.getCmd(), 6)) { + obj.free(); + break; + } + obj.free(); + } + end = lexer->getPos(); + + lexer->setPos(start); + written += stream_copy(str, tempFp, end - start); + fputc('\n', tempFp); + written++; + // Add annotation object + offAnn = written; + written += fprintf(tempFp, "%d 0 obj\n", numObjects); + written += fprintf(tempFp, "%s", annotationObject->getCString()); + written += fprintf(tempFp, "/AP\n<<\n/N %d 0 R\n>>\n>>\nendobj\n", numObjects + 1); + numObjects++; + // Add appereance object + + offAp = written; + written += fprintf(tempFp, "%d 0 obj\n", numObjects); + written += fprintf(tempFp, "%s", appereanceObject->getCString()); + + delete annotationObject; + delete appereanceObject; + delete appereanceStream; + + /* XREF */ + int the_ref; // reference to put in the xref + int the_offset; // offset to put in the xref + if (annotList->getNumAnnots() == 0) { // no pre-existing annotations + the_ref = ref->num; + the_offset = offPage; + } else { + if (page->getAnnotNum() != -1) { // pre-existing annotations array + the_ref = page-> getAnnotNum(); + the_offset = offPage; + } else { // inline annotation + the_ref = ref->num; + the_offset = offPage; + } + } + fprintf(tempFp, + "xref\n"\ + "%d 1\n"\ + "%010d 00000 n \n"\ + "%d 2\n"\ + "%010d 00000 n \n"\ + "%010d 00000 n \n", + the_ref, the_offset, numObjects - 1, offAnn, offAp); + + /* TRAILER */ + Object *dict = xref->getTrailerDict(); + fprintf(tempFp, "trailer\n<<\n"); + for (i = 0; i < dict->dictGetLength(); ++i) { + if (strncmp("Size", dict->dictGetKey(i), 4) == 0) { + fprintf(tempFp, "/Size %d\n", numObjects + 1); + } else if (strncmp("Root", dict->dictGetKey(i), 4) == 0) { + fprintf(tempFp, "/Root %d %d R\n", + xref->getRootNum(), xref->getRootGen()); + } else if (strncmp("Prev", dict->dictGetKey(i), 4) == 0) { + fprintf(tempFp, "/Prev %d\n", xref->getLastXRefPos()); + } else { + fprintf(tempFp, "/%s ", dict->dictGetKey(i)); + dict->dictGetValNF(i, &obj); + obj.print(tempFp); + obj.free(); + fprintf(tempFp, "\n"); + } + } + fprintf(tempFp, ">>\n"); + fprintf(tempFp, "startxref\n%d\n%%%%EOF\n", written); + + delete annotList; + fclose(tempFp); + str->close(); + + GString *tempFileName = fileName->copy()->append(".temp"); + if (!temporary) + return tempFileName; + rename(tempFileName->getCString(), fileName->getCString()); + delete tempFileName; + return fileName->copy(); +} + +// write len spaces to the file, used to overwrite 'deleted' data +static void write_spaces(FILE *fp, int len) { + int i; + for (i = 0; i < len; ++i) + fputc(' ', fp); +} + +// remove annotation n on page pg +GString *PDFDoc::delTempAnnotation(int pg, int n, GBool temporary) { + XRefEntry *target; + Object obj; + FILE *tempFp; + int i, start, end, c; + + tempFp = make_temp_copy(fileName, str, NULL); + if (tempFp == NULL) { /*ERROR*/ + return NULL; + } + + str->reset(); + Lexer *lexer = new Lexer(xref, str); + Page *page = catalog->getPage(pg); + if (page->getAnnotNum() != -1) { // annotation in the array + target = xref->getEntry(page->getAnnotNum()); + lexer->setPos(target->offset); + lexer->getObj(&obj); // annotation array number + obj.free(); + lexer->getObj(&obj); // annotation array gen + obj.free(); + lexer->getObj(&obj); // "obj" string + obj.free(); + } else { // inline annotation + target = xref->getEntry(catalog->getPageRef(pg)->num); + lexer->setPos(target->offset); + // locate the Annots object + while(1) { + lexer->getObj(&obj); + if (obj.isName() && (strncmp("Annots", obj.getName(), 6) == 0)) { + obj.free(); + break; + } + obj.free(); + } + } + lexer->getObj(&obj); // "[" begin of array + obj.free(); + // skip the first n-1 annotations, jump to target annotation + for (i = 0; i < n; ++i) { + lexer->getObj(&obj); // annotation number + obj.free(); + lexer->getObj(&obj); // annotation gen + obj.free(); + lexer->getObj(&obj); // "R" + obj.free(); + } + + // compute the width of the target annotation + start = lexer->getPos(); + lexer->getObj(&obj); // annotation number + n = obj.getInt(); + obj.free(); + lexer->getObj(&obj); // annotation gen + obj.free(); + lexer->getObj(&obj); // "R" string + obj.free(); + end = lexer->getPos(); + + // overwrite data with spaces + fseek(tempFp, start, SEEK_SET); + write_spaces(tempFp, end - start); + + // now locate and compute size of the annotation object + target = xref->getEntry(n); + lexer->setPos(target->offset); + start = target->offset; + while (1) { + lexer->getObj(&obj); + if (obj.isName() && (strncmp("N", obj.getName(), 1) == 0)) { + obj.free(); + lexer->getObj(&obj); + n = obj.getInt(); + } else if (obj.isCmd() && (strncmp("endobj", obj.getCmd(), 6) == 0)) { + obj.free(); + break; + } + obj.free(); + } + end = lexer->getPos(); + + fseek(tempFp, start, SEEK_SET); + write_spaces(tempFp, end - start); + + // and finally do the same for the stream + target = xref->getEntry(n); + lexer->setPos(target->offset); + start = target->offset; + while (1) { + lexer->getObj(&obj); + if (obj.isName() && (strncmp("Length", obj.getName(), 6) == 0)) { + obj.free(); + lexer->getObj(&obj); + n = obj.getInt(); + } else if (obj.isCmd() && (strncmp("stream", obj.getCmd(), 6) == 0)) { + lexer->setPos(lexer->getPos() + n); + } else if (obj.isCmd() && (strncmp("endobj", obj.getCmd(), 6) == 0)) { + obj.free(); + break; + } + obj.free(); + } + end = lexer->getPos(); + + fseek(tempFp, start, SEEK_SET); + write_spaces(tempFp, end - start); // overwrite data with spaces + + str->close(); + fclose(tempFp); + + GString *tempFileName = fileName->copy()->append(".temp"); + if (!temporary) + return tempFileName; + rename(tempFileName->getCString(), fileName->getCString()); + delete tempFileName; + return fileName->copy(); +} Index: xpdf/GlobalParams.cc =================================================================== --- xpdf/GlobalParams.cc (revision 1) +++ xpdf/GlobalParams.cc (working copy) @@ -444,6 +444,17 @@ cmds->append(new GString(cmd1)); } +KeyBinding::KeyBinding(int codeA, int modsA, int contextA, + char *cmd0, char *cmd1, char *cmd2) { + code = codeA; + mods = modsA; + context = contextA; + cmds = new GList(); + cmds->append(new GString(cmd0)); + cmds->append(new GString(cmd1)); + cmds->append(new GString(cmd2)); +} + KeyBinding::KeyBinding(int codeA, int modsA, int contextA, GList *cmdsA) { code = codeA; mods = modsA; @@ -792,10 +803,9 @@ //----- mouse buttons keyBindings->append(new KeyBinding(xpdfKeyCodeMousePress1, xpdfKeyModNone, - xpdfKeyContextAny, "startSelection")); + xpdfKeyContextAny, "startDraw", "startSelection")); keyBindings->append(new KeyBinding(xpdfKeyCodeMouseRelease1, xpdfKeyModNone, - xpdfKeyContextAny, "endSelection", - "followLinkNoSel")); + xpdfKeyContextAny, "endDraw", "endSelection", "followLinkNoSel")); keyBindings->append(new KeyBinding(xpdfKeyCodeMousePress2, xpdfKeyModNone, xpdfKeyContextAny, "startPan")); keyBindings->append(new KeyBinding(xpdfKeyCodeMouseRelease2, xpdfKeyModNone, @@ -828,7 +838,7 @@ keyBindings->append(new KeyBinding(xpdfKeyCodeBackspace, xpdfKeyModNone, xpdfKeyContextAny, "pageUp")); keyBindings->append(new KeyBinding(xpdfKeyCodeDelete, xpdfKeyModNone, - xpdfKeyContextAny, "pageUp")); + xpdfKeyContextAny, "deleteAnnotation")); keyBindings->append(new KeyBinding(xpdfKeyCodePgDn, xpdfKeyModNone, xpdfKeyContextAny, "pageDown")); keyBindings->append(new KeyBinding(' ', xpdfKeyModNone, Index: xpdf/PDFCore.cc =================================================================== --- xpdf/PDFCore.cc (revision 1) +++ xpdf/PDFCore.cc (working copy) @@ -84,6 +84,8 @@ int i; doc = NULL; + modified = gFalse; + temporary = gFalse; continuousMode = globalParams->getContinuousView(); drawAreaWidth = drawAreaHeight = 0; maxPageW = totalDocH = 0; @@ -91,12 +93,19 @@ topPage = 0; scrollX = scrollY = 0; zoom = defZoom; + color[0] = 0xff; color[1] = color[2] = 0x00; + thickness = 1; dpi = 0; rotate = 0; selectPage = 0; selectULX = selectLRX = 0; selectULY = selectLRY = 0; + + drawing = drawn = gFalse; + lastDrawnX = lastDrawnY = -1; + lastStreamX = lastStreamY = -1; + dragging = gFalse; lastDragLeft = lastDragTop = gTrue; @@ -857,6 +866,8 @@ GBool PDFCore::gotoNextPage(int inc, GBool top) { int pg, scrollYA; + if (drawn) + finalizeDraw(); if (!doc || doc->getNumPages() == 0 || topPage >= doc->getNumPages()) { return gFalse; } @@ -1304,7 +1315,6 @@ SplashColor xorColor; PDFCorePage *page; - haveSel = selectULX != selectLRX && selectULY != selectLRY; newHaveSel = newSelectULX != newSelectLRX && newSelectULY != newSelectLRY; @@ -1491,6 +1501,145 @@ newSelectLRX, newSelectLRY); } +// finalize a pending draw command, by collating all pieces of info +// into the final file. +GBool PDFCore::finalizeDraw() { + int pg; + GString *s = *doc->getAnnotationObject(); + if (!s) + return gFalse; + // add te bounding box + s->appendf( + "]\n"\ + "/Rect[{0:.1f} {1:.1f} {2:.1f} {3:.1f}]\n", + rectXmin - thickness, rectYmin - thickness, rectXmax + thickness, rectYmax + thickness); + + *doc->getAppereanceObject() = GString::format( + "<<\n"\ + "/Matrix [1.0 0.0 0.0 1.0 {0:.1f} {1:.1f}]\n"\ + "/Subtype /Form\n"\ + "/Length {2:d}\n"\ + "/FormType 1\n"\ + "/Type /XObject\n"\ + "/BBox[{3:.1f} {4:.1f} {5:.1f} {6:.1f}]\n"\ + ">>\n"\ + "stream\n"\ + "{7:t}"\ + "endstream\n"\ + "endobject\n", + -rectXmin + thickness, -rectYmin + thickness, + (*doc->getAppereanceStream())->getLength(), + rectXmin - thickness, rectYmin - thickness, rectXmax + thickness, rectYmax + thickness, + (*doc->getAppereanceStream())); + + drawn = gFalse; + pg = topPage; + GString *temp = doc->addTempAnnotation(pg, getTemporary()); // temp is the file to be loaded + if (temp == NULL) + return gFalse; + setTemporary(gTrue); + setModified(gTrue); + loadFile(temp); + displayPage(pg, getZoom(), getRotate(), gFalse, gFalse); + delete temp; + return gTrue; +} + +// mdraw the next point +void PDFCore::drawPoint(int pg, int x, int y) { + int newSelectULX, newSelectULY, newSelectLRX, newSelectLRY; + GBool haveSel; + SplashColor xorColor; + PDFCorePage *page; + SplashPattern *pattern; + int i; + + haveSel = selectULX != selectLRX && selectULY != selectLRY; + + if (haveSel) { + xorColor[0] = xorColor[1] = xorColor[2] = 0xff; + xorRectangle(selectPage, selectULX, selectULY, selectLRX, selectLRY, + new SplashSolidColor(xorColor)); + } + page = findPage(pg); + if (!page) + return; + if (haveSel) { + redrawWindow(page->xDest + selectULX, page->yDest + selectULY, + selectLRX - selectULX + 1, selectLRY - selectULY + 1, gFalse); + } + selectULX = selectLRX = selectULY = selectLRY = 0; + xorColor[0] = color[0]; + xorColor[1] = color[1]; + xorColor[2] = color[2]; + pattern = new SplashSolidColor(xorColor); + + // The page can have multiple tiles, so we work on each of them. + // In practice, most of the times we have only one tile. + for (i = 0; i < page->tiles->getLength(); ++i) { + int xi = 0, yi = 0, wi = 0, hi = 0; + PDFCoreTile *tile = (PDFCoreTile *)page->tiles->get(i); + Splash *splash = new Splash(tile->bitmap, gFalse); + + splash->setFillPattern(pattern->copy()); + if (lastDrawnX != -1 && lastDrawnY != -1) { + // draw a line from the previous point (lastDrawnX, lastDrawnY) + // to the current one. + SplashCoord xx0 = (SplashCoord)(lastDrawnX - tile->xMin); + SplashCoord yy0 = (SplashCoord)(lastDrawnY - tile->yMin); + SplashCoord xx1 = (SplashCoord)(x - tile->xMin); + SplashCoord yy1 = (SplashCoord)(y - tile->yMin); + SplashPath *path = new SplashPath(); + path->moveTo(xx0, yy0); + path->lineTo(xx1, yy1); + path->close(); + splash->fill(path, gTrue); + delete path; + delete splash; + + // the following code computes the size of the region to redraw + // which goes between min(x, lastDrawnX) and max(...) + // on both coordinates. + // Also if the region is outside the tile, we truncate it. + if (x < lastDrawnX) { + xi = x - tile->xMin; + wi = lastDrawnX - x; + } else { + xi = lastDrawnX - tile->xMin; + wi = x - lastDrawnX; + } + if (xi < 0) { + wi += xi; + xi = 0; + } + if (xi + wi > tile->bitmap->getWidth()) { + wi = tile->bitmap->getWidth() - xi; + } + if (y < lastDrawnY) { + yi = y - tile->yMin; + hi = lastDrawnY - y; + } else { + yi = lastDrawnY - tile->yMin; + hi = y - lastDrawnY; + } + if (yi < 0) { + hi += yi; + yi = 0; + } + if (yi + hi > tile->bitmap->getHeight()) { + hi = tile->bitmap->getHeight() - yi; + } + wi = wi ? wi : 1; // make sure size is at least 1 pixel + hi = hi ? hi : 1; // make sure size is at least 1 pixel + updateTileData(tile, xi, yi, wi, hi, gTrue); + redrawWindow(page->xDest + xi, page->yDest + yi, wi, hi, gFalse); + } + lastDrawnX = x; + lastDrawnY = y; + } + delete pattern; +} + void PDFCore::xorRectangle(int pg, int x0, int y0, int x1, int y1, SplashPattern *pattern, PDFCoreTile *oneTile) { Splash *splash; Index: xpdf/Gfx.cc =================================================================== --- xpdf/Gfx.cc (revision 1) +++ xpdf/Gfx.cc (working copy) @@ -445,7 +445,6 @@ subPage = gFalse; printCommands = globalParams->getPrintCommands(); - // start the resource stack res = new GfxResources(xref, resDict, NULL); // initialize @@ -4037,6 +4036,7 @@ y = annotY0; annotY0 = annotY1; annotY1 = y; } + //printf("%f %f\n",x , y); // draw the appearance stream (if there is one) if (str->isStream()) { Index: xpdf/about-text.h =================================================================== --- xpdf/about-text.h (revision 1) +++ xpdf/about-text.h (working copy) @@ -19,7 +19,7 @@ "are copyright 1985-2006 Adobe Systems Inc.", " ", "Mouse bindings:", - " button 1: select text / follow link", + " button 1: select text / follow link / select annotations / draw annotations", " button 2: pan window", " button 3: menu", " ", @@ -32,7 +32,7 @@ " n = next page", " p = previous page", " = = scroll down", - " = = = scroll up", + " = = scroll up", " v = forward (history path)", " b = backward (history path)", " 0 / + / - = zoom zero / in / out", @@ -42,6 +42,7 @@ " q = quit", " / = top / bottom of page", " = scroll", + " = delete selected annotation", " ", "For more information, please read the xpdf(1) man page.", NULL Index: xpdf/XPDFViewer.cc =================================================================== --- xpdf/XPDFViewer.cc (revision 1) +++ xpdf/XPDFViewer.cc (working copy) @@ -138,7 +138,7 @@ double zoom; }; -static ZoomMenuInfo zoomMenuInfo[nZoomMenuItems] = { +static ZoomMenuInfo zoomMenuInfo[] = { { "400%", 400 }, { "200%", 200 }, { "150%", 150 }, @@ -151,6 +151,48 @@ { "fit width", zoomWidth } }; +struct ThicknessMenuInfo { + char *label; + int value; +}; + +static ThicknessMenuInfo thicknessMenuInfo[] = { + { " 1" , 1 }, + { " 2" , 2 }, + { " 3" , 3 }, + { " 4" , 4 }, + { " 5" , 5 }, + { " 6" , 6 }, + { " 7" , 7 }, + { " 8" , 8 }, + { " 9" , 9 }, + { "10" , 10 }, + { "11" , 11 }, + { "12" , 12 }, + { "13" , 13 }, + { "14" , 14 }, + { "15" , 15 }, + { "16" , 16 } +}; + +struct ColorMenuInfo { + char* label; + Guchar R; + Guchar G; + Guchar B; +}; + +static ColorMenuInfo colorMenuInfo[] = { + {"Black", 0x00, 0x00, 0x00}, + {"Blue", 0x00, 0x00, 0xff}, + {"Green", 0x00, 0xff, 0x00}, + {"Cyan", 0x00, 0xff, 0xff}, + {"Red", 0xff, 0x00, 0x00}, + {"Magenta", 0xff, 0x00, 0xff}, + {"Yellow", 0xff, 0xff, 0x00}, + {"White", 0xff, 0xff, 0xff} +}; + #define maxZoomIdx 0 #define defZoomIdx 3 #define minZoomIdx 7 @@ -166,6 +208,8 @@ { "closeOutline", 0, gFalse, gFalse, &XPDFViewer::cmdCloseOutline }, { "closeWindow", 0, gFalse, gFalse, &XPDFViewer::cmdCloseWindow }, { "continuousMode", 0, gFalse, gFalse, &XPDFViewer::cmdContinuousMode }, + { "deleteAnnotation", 0, gTrue, gTrue, &XPDFViewer::cmdDeleteAnnotation }, + { "endDraw", 0, gTrue, gTrue, &XPDFViewer::cmdEndDraw}, { "endPan", 0, gTrue, gTrue, &XPDFViewer::cmdEndPan }, { "endSelection", 0, gTrue, gTrue, &XPDFViewer::cmdEndSelection }, { "find", 0, gTrue, gFalse, &XPDFViewer::cmdFind }, @@ -221,6 +265,7 @@ { "scrollUp", 1, gTrue, gFalse, &XPDFViewer::cmdScrollUp }, { "scrollUpPrevPage", 1, gTrue, gFalse, &XPDFViewer::cmdScrollUpPrevPage }, { "singlePageMode", 0, gFalse, gFalse, &XPDFViewer::cmdSinglePageMode }, + { "startDraw", 0, gTrue, gTrue, &XPDFViewer::cmdStartDraw }, { "startPan", 0, gTrue, gTrue, &XPDFViewer::cmdStartPan }, { "startSelection", 0, gTrue, gTrue, &XPDFViewer::cmdStartSelection }, { "toggleContinuousMode", 0, gFalse, gFalse, &XPDFViewer::cmdToggleContinuousMode }, @@ -412,6 +457,7 @@ XtVaSetValues(prevPageBtn, XmNsensitive, False, NULL); XtVaSetValues(nextTenPageBtn, XmNsensitive, False, NULL); XtVaSetValues(nextPageBtn, XmNsensitive, False, NULL); + XtVaSetValues(drawBtn, XmNsensitive, False, NULL); // remove the old outline #ifndef DISABLE_OUTLINE @@ -488,6 +534,7 @@ double xu, yu, selULX, selULY, selLRX, selLRY; if (core->getHyperlinksEnabled() && + !core->getDrawEnabled() && core->cvtWindowToUser(wx, wy, &pg, &xu, &yu) && !(onlyIfNoSelection && core->getSelection(&selPg, &selULX, &selULY, &selLRX, &selLRY))) { @@ -702,7 +749,6 @@ if (cmp != 0) { goto err1; } - //----- execute the command if (nArgs != cmdTab[a].nArgs || (cmdTab[a].requiresEvent && !event)) { @@ -789,10 +835,31 @@ #endif } +GBool XPDFViewer::save_helper() { + GBool flag = gFalse; + + if (core->getTemporary()) { + GString *msg = new GString("The file has been modified:\nwould you like to save changes?"); + flag = core->doQuestionDialog("Xpdf", msg); + delete msg; + } + if (flag) { + mapSaveAsDialog(); + } else { + if (core->getTemporary() && core->getDoc() != NULL) + remove(core->getDoc()->getFileName()->getCString()); + // we close the document anyways XXX not always + } + core->setTemporary(gFalse); + return flag; +} + void XPDFViewer::cmdCloseWindow(GString *args[], int nArgs, XEvent *event) { + if (!save_helper()) { app->close(this, gFalse); } +} void XPDFViewer::cmdContinuousMode(GString *args[], int nArgs, XEvent *event) { @@ -807,6 +874,16 @@ XtVaSetValues(btn, XmNset, XmSET, NULL); } +void XPDFViewer::cmdDeleteAnnotation(GString *args[], int nArgs, + XEvent *event) { + core->deleteAnnotation(); +} + +void XPDFViewer::cmdEndDraw(GString *args[], int nArgs, + XEvent *event) { + core->endDraw(mouseX(event), mouseY(event)); +} + void XPDFViewer::cmdEndPan(GString *args[], int nArgs, XEvent *event) { core->endPan(mouseX(event), mouseY(event)); @@ -949,18 +1026,24 @@ void XPDFViewer::cmdOpen(GString *args[], int nArgs, XEvent *event) { + if (!save_helper()) { mapOpenDialog(gFalse); } +} void XPDFViewer::cmdOpenFile(GString *args[], int nArgs, XEvent *event) { + if (!save_helper()) { open(args[0], 1, NULL); } +} void XPDFViewer::cmdOpenFileAtDest(GString *args[], int nArgs, XEvent *event) { + if (!save_helper()) { open(args[0], 1, args[1]); } +} void XPDFViewer::cmdOpenFileAtDestInNewWin(GString *args[], int nArgs, XEvent *event) { @@ -969,8 +1052,10 @@ void XPDFViewer::cmdOpenFileAtPage(GString *args[], int nArgs, XEvent *event) { + if (!save_helper()) { open(args[0], atoi(args[1]->getCString()), NULL); } +} void XPDFViewer::cmdOpenFileAtPageInNewWin(GString *args[], int nArgs, XEvent *event) { @@ -1046,8 +1131,10 @@ void XPDFViewer::cmdQuit(GString *args[], int nArgs, XEvent *event) { + if (!save_helper()) { app->quit(); } +} void XPDFViewer::cmdRaise(GString *args[], int nArgs, XEvent *event) { @@ -1263,6 +1350,11 @@ XtVaSetValues(btn, XmNset, XmUNSET, NULL); } +void XPDFViewer::cmdStartDraw(GString *args[], int nArgs, + XEvent *event) { + core->startDraw(mouseX(event), mouseY(event)); +} + void XPDFViewer::cmdStartPan(GString *args[], int nArgs, XEvent *event) { core->startPan(mouseX(event), mouseY(event)); @@ -1527,6 +1619,8 @@ NULL); XtVaSetValues(printBtn, XmNnavigationType, XmEXCLUSIVE_TAB_GROUP, NULL); + XtVaSetValues(colorWidget, XmNnavigationType, XmEXCLUSIVE_TAB_GROUP, + NULL); XtVaSetValues(aboutBtn, XmNnavigationType, XmEXCLUSIVE_TAB_GROUP, NULL); XtVaSetValues(quitBtn, XmNnavigationType, XmEXCLUSIVE_TAB_GROUP, @@ -1789,8 +1883,145 @@ XtManageChild(aboutBtn); XtAddCallback(aboutBtn, XmNactivateCallback, &aboutCbk, (XtPointer)this); - lastBtn = aboutBtn; + //thickness menu +#if USE_COMBO_BOX + XmString st3[nThicknessMenuItems]; + n = 0; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); ++n; + XtSetArg(args[n], XmNleftWidget, aboutBtn); ++n; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); ++n; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); ++n; + XtSetArg(args[n], XmNmarginWidth, 0); ++n; + XtSetArg(args[n], XmNmarginHeight, 0); ++n; + XtSetArg(args[n], XmNcomboBoxType, XmDROP_DOWN_LIST); ++n; + XtSetArg(args[n], XmNpositionMode, XmONE_BASED); ++n; + XtSetArg(args[n], XmNcolumns, 3); ++n; + for (i = 0; i < nThicknessMenuItems ; ++i) { + st3[i] = XmStringCreateLocalized(thicknessMenuInfo[i].label); + } + XtSetArg(args[n], XmNitems, st3); ++n; + XtSetArg(args[n], XmNitemCount, nThicknessMenuItems); ++n; + XtSetArg(args[n], XmNvisibleItemCount, nThicknessMenuItems); ++n; + thicknessComboBox = XmCreateComboBox(toolBar, "thicknessComboBox", args, n); + for (i = 0; i < nThicknessMenuItems; ++i) { + XmStringFree(st3[i]); + } + addToolTip(thicknessComboBox, "Annotation Thickness"); + XtAddCallback(thicknessComboBox, XmNselectionCallback, + &thicknessComboBoxCbk, (XtPointer)this); + XtManageChild(thicknessComboBox); + thicknessWidget = thicknessComboBox; +#else + Widget menuPane2; + n = 0; + menuPane2 = XmCreatePulldownMenu(toolBar, "thicknessMenuPane", args, n); + for (i = 0; i < nThicknessMenuItems; ++i) { + n = 0; + s = XmStringCreateLocalized(thicknessMenuInfo[i].label); + XtSetArg(args[n], XmNlabelString, s); ++n; + XtSetArg(args[n], XmNuserData, (XtPointer)i); ++n; + sprintf(buf, "thickness%d", i); + btn = XmCreatePushButton(menuPane2, buf, args, n); + XmStringFree(s); + XtManageChild(btn); + XtAddCallback(btn, XmNactivateCallback, + &thicknessMenuCbk, (XtPointer)this); + thicknessMenuBtns[i] = btn; + } + n = 0; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); ++n; + XtSetArg(args[n], XmNleftWidget, aboutBtn); ++n; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); ++n; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); ++n; + XtSetArg(args[n], XmNmarginWidth, 0); ++n; + XtSetArg(args[n], XmNmarginHeight, 0); ++n; + XtSetArg(args[n], XmNselectedPosition, 5); ++n; + XtSetArg(args[n], XmNsubMenuId, menuPane2); ++n; + thicknessMenu = XmCreateOptionMenu(toolBar, "thicknessMenu", args, n); + addToolTip(thicknessMenu, "Thickness"); + XtManageChild(thicknessMenu); + thicknessWidget = thicknessMenu; +#endif + + //color menu +#if USE_COMBO_BOX + XmString st2[nColorMenuItems]; + n = 0; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); ++n; + XtSetArg(args[n], XmNleftWidget, thicknessWidget); ++n; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); ++n; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); ++n; + XtSetArg(args[n], XmNmarginWidth, 0); ++n; + XtSetArg(args[n], XmNmarginHeight, 0); ++n; + XtSetArg(args[n], XmNcomboBoxType, XmDROP_DOWN_LIST); ++n; + XtSetArg(args[n], XmNpositionMode, XmONE_BASED); ++n; + XtSetArg(args[n], XmNselectedPosition, 5); ++n; + XtSetArg(args[n], XmNcolumns, 7); ++n; + for (i = 0; i < nColorMenuItems ; ++i) { + st2[i] = XmStringCreateLocalized(colorMenuInfo[i].label); + } + XtSetArg(args[n], XmNitems, st2); ++n; + XtSetArg(args[n], XmNitemCount, nColorMenuItems); ++n; + XtSetArg(args[n], XmNvisibleItemCount, nColorMenuItems); ++n; + colorComboBox = XmCreateComboBox(toolBar, "colorComboBox", args, n); + for (i = 0; i < nColorMenuItems; ++i) { + XmStringFree(st2[i]); + } + addToolTip(colorComboBox, "Annotation Color"); + XtAddCallback(colorComboBox, XmNselectionCallback, + &colorComboBoxCbk, (XtPointer)this); + XtManageChild(colorComboBox); + colorWidget = colorComboBox; +#else + Widget menuPane1; + n = 0; + menuPane1 = XmCreatePulldownMenu(toolBar, "colorMenuPane", args, n); + for (i = 0; i < nColorMenuItems; ++i) { + n = 0; + s = XmStringCreateLocalized(colorMenuInfo[i].label); + XtSetArg(args[n], XmNlabelString, s); ++n; + XtSetArg(args[n], XmNuserData, (XtPointer)i); ++n; + sprintf(buf, "color%d", i); + btn = XmCreatePushButton(menuPane1, buf, args, n); + XmStringFree(s); + XtManageChild(btn); + XtAddCallback(btn, XmNactivateCallback, + &colorMenuCbk, (XtPointer)this); + colorMenuBtns[i] = btn; + } + n = 0; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); ++n; + XtSetArg(args[n], XmNleftWidget, aboutBtn); ++n; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); ++n; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); ++n; + XtSetArg(args[n], XmNmarginWidth, 0); ++n; + XtSetArg(args[n], XmNmarginHeight, 0); ++n; + XtSetArg(args[n], XmNselectedPosition, 5); ++n; + XtSetArg(args[n], XmNsubMenuId, menuPane1); ++n; + colorMenu = XmCreateOptionMenu(toolBar, "colorMenu", args, n); + addToolTip(colorMenu, "Color"); + XtManageChild(colorMenu); + colorWidget = colorMenu; +#endif + + // draw button + n = 0; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); ++n; + XtSetArg(args[n], XmNleftWidget, colorWidget); ++n; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); ++n; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); ++n; + XtSetArg(args[n], XmNmarginWidth, 6); ++n; + XtSetArg(args[n], XmNsensitive, False); ++n; + s = XmStringCreateLocalized("Draw"); + XtSetArg(args[n], XmNlabelString, s); ++n; + drawBtn = XmCreatePushButton(toolBar, "draw", args, n); + XmStringFree(s); + XtManageChild(drawBtn); + XtAddCallback(drawBtn, XmNactivateCallback, + &drawCbk, (XtPointer)this); + lastBtn = drawBtn; + // quit button n = 0; XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); ++n; @@ -1980,6 +2211,14 @@ XtAddCallback(btn, XmNactivateCallback, &zoomToSelectionCbk, (XtPointer)this); n = 0; + s = XmStringCreateLocalized("Draw / End Draw"); + XtSetArg(args[n], XmNlabelString, s); ++n; + btn = XmCreatePushButton(popupMenu, "draw", args, n); + XmStringFree(s); + XtManageChild(btn); + XtAddCallback(btn, XmNactivateCallback, + &drawCbk, (XtPointer)this); + n = 0; btn = XmCreateSeparator(popupMenu, "sep2", args, n); XtManageChild(btn); n = 0; @@ -2380,6 +2619,26 @@ viewer->core->takeFocus(); } +void XPDFViewer::thicknessComboBoxCbk(Widget widget, XtPointer ptr, + XtPointer callData) { + XPDFViewer *viewer = (XPDFViewer *)ptr; + XmComboBoxCallbackStruct *data = (XmComboBoxCallbackStruct *)callData; + + viewer->core->setThickness(thicknessMenuInfo[data->item_position - 1].value); +} + +void XPDFViewer::colorComboBoxCbk(Widget widget, XtPointer ptr, + XtPointer callData) { + XPDFViewer *viewer = (XPDFViewer *)ptr; + XmComboBoxCallbackStruct *data = (XmComboBoxCallbackStruct *)callData; + + viewer->core->setColor( + colorMenuInfo[data->item_position -1].R, + colorMenuInfo[data->item_position -1].G, + colorMenuInfo[data->item_position -1].B + ); +} + #else // USE_COMBO_BOX void XPDFViewer::zoomMenuCbk(Widget widget, XtPointer ptr, @@ -2400,6 +2659,30 @@ viewer->core->takeFocus(); } +void XPDFViewer::thicknessMenuCbk(Widget widget, XtPointer ptr, + XtPointer callData) { + XPDFViewer *viewer = (XPDFViewer *)ptr; + XmPushButtonCallbackStruct *data = (XmPushButtonCallbackStruct *)callData; + XtPointer userData; + + XtVaGetValues(widget, XmNuserData, &userData, NULL); + viewer->core->setColor(thicknessMenuInfo[(long)userData].value); +} + +void XPDFViewer::colorMenuCbk(Widget widget, XtPointer ptr, + XtPointer callData) { + XPDFViewer *viewer = (XPDFViewer *)ptr; + XmPushButtonCallbackStruct *data = (XmPushButtonCallbackStruct *)callData; + XtPointer userData; + + XtVaGetValues(widget, XmNuserData, &userData, NULL); + viewer->core->setColor( + colorMenuInfo[(long)userData].R, + colorMenuInfo[(long)userData].G, + colorMenuInfo[(long)userData].B + ); +} + #endif // USE_COMBO_BOX void XPDFViewer::findCbk(Widget widget, XtPointer ptr, @@ -2429,19 +2712,56 @@ XtManageChild(viewer->aboutDialog); } +void XPDFViewer::drawCbk(Widget widget, XtPointer ptr, + XtPointer callData) { + Arg args[1]; + int n = 0; + XmString label_str; + + XPDFViewer *viewer = (XPDFViewer *)ptr; + if (!viewer->core->getDrawEnabled()) { + label_str = XmStringCreateLocalized("End Draw"); + if (!viewer->core->getFullScreen()) { + // disable color, and thickness drop down menu + XtVaSetValues(viewer->colorWidget, XmNsensitive, False, NULL); + XtVaSetValues(viewer->thicknessWidget, XmNsensitive, False, NULL); + } + viewer->core->enableDraw(gTrue); + } else { + label_str = XmStringCreateLocalized("Draw"); + if (!viewer->core->getFullScreen()) { + // enable color, and thickness drop down menu + XtVaSetValues(viewer->colorComboBox, XmNsensitive, True, NULL); + XtVaSetValues(viewer->thicknessComboBox, XmNsensitive, True, NULL); + } + if (viewer->core->getDrawn()) { + viewer->core->finalizeDraw(); + } + viewer->core->enableDraw(gFalse); + } + XtSetArg(args[n],XmNlabelString, label_str); ++n; + if (!viewer->core->getFullScreen()) + XtSetValues(viewer->drawBtn, args, n); + XmStringFree(label_str); +} + void XPDFViewer::quitCbk(Widget widget, XtPointer ptr, XtPointer callData) { XPDFViewer *viewer = (XPDFViewer *)ptr; + if (!viewer->save_helper()) { viewer->app->quit(); } +} void XPDFViewer::openCbk(Widget widget, XtPointer ptr, XtPointer callData) { XPDFViewer *viewer = (XPDFViewer *)ptr; + if (!viewer->save_helper()) { viewer->mapOpenDialog(gFalse); } +} void XPDFViewer::openInNewWindowCbk(Widget widget, XtPointer ptr, XtPointer callData) { @@ -2526,8 +2846,10 @@ XtPointer callData) { XPDFViewer *viewer = (XPDFViewer *)ptr; + if (!viewer->save_helper()) { viewer->app->close(viewer, gFalse); } +} void XPDFViewer::closeMsgCbk(Widget widget, XtPointer ptr, XtPointer callData) { @@ -2622,8 +2944,15 @@ XtVaSetValues(viewer->linkLabel, XmNlabelString, s, NULL); XmStringFree(s); } + + if (globalParams->getPSFile() == NULL && // NOT a Postscript file + viewer->core->getDoc() != NULL && // a PDF doc is open + !viewer->core->getDoc()->isEncrypted()) { // the document is not encrypted + XtVaSetValues(viewer->drawBtn, XmNsensitive, + gTrue, NULL); } } +} //------------------------------------------------------------------------ @@ -2909,6 +3238,9 @@ Boolean sep; GString *fileNameStr; + if (!viewer->openInNewWindow) { + viewer->core->setModified(gFalse); + } XmStringInitContext(&context, data->value); if (XmStringGetNextSegment(context, &fileName, &charSet, &dir, &sep)) { fileNameStr = new GString(fileName); @@ -3139,7 +3471,7 @@ XmFileSelectionBoxCallbackStruct *data = (XmFileSelectionBoxCallbackStruct *)callData; char *fileName; - GString *fileNameStr; + GString *fileNameStr, *temp; XmStringContext context; XmStringCharSet charSet; XmStringDirection dir; @@ -3149,6 +3481,17 @@ if (XmStringGetNextSegment(context, &fileName, &charSet, &dir, &sep)) { fileNameStr = new GString(fileName); viewer->core->getDoc()->saveAs(fileNameStr); + temp = viewer->core->getDoc()->getFileName()->copy(); + makePathAbsolute(temp); + if (strcmp(temp->getCString(), + fileNameStr->getCString()) == 0) { + // if the old name and the new one are equal, it's + // better to reset the temporary flag; if the user try + // to save the file using the name of the temporary file, + // on "quit event" xpdf could delete the file!!! + viewer->core->setTemporary(gFalse); + } + delete temp; delete fileNameStr; XtFree(charSet); XtFree(fileName); Index: xpdf/PSOutputDev.cc =================================================================== --- xpdf/PSOutputDev.cc (revision 1) +++ xpdf/PSOutputDev.cc (working copy) @@ -4853,8 +4853,8 @@ // delete encoders if (useRLE || useASCII || inlineImg) { delete str; - } } + } if ((maskColors && colorMap && !inlineImg) || maskStr) { writePS("pdfImClipEnd\n"); Index: xpdf/GlobalParams.h =================================================================== --- xpdf/GlobalParams.h (revision 1) +++ xpdf/GlobalParams.h (working copy) @@ -137,6 +137,7 @@ KeyBinding(int codeA, int modsA, int contextA, char *cmd0); KeyBinding(int codeA, int modsA, int contextA, char *cmd0, char *cmd1); + KeyBinding(int codeA, int modsA, int contextA, char *cmd0, char *cmd1, char *cmd2); KeyBinding(int codeA, int modsA, int contextA, GList *cmdsA); ~KeyBinding(); }; Index: xpdf/Annot.cc =================================================================== --- xpdf/Annot.cc (revision 1) +++ xpdf/Annot.cc (working copy) @@ -242,6 +242,10 @@ borderDash, borderDashLength, borderR, borderG, borderB); + //----- parse the inkList + + dict->lookup("InkList", &inkList); + //----- get the annotation appearance if (dict->lookup("AP", &apObj)->isDict()) { @@ -275,6 +279,7 @@ delete type; } appearance.free(); + inkList.free(); if (appearBuf) { delete appearBuf; } @@ -648,6 +653,61 @@ mkObj.free(); } +static void get_coord(Object &array, int pos, double &ret) { + Object coord; + array.arrayGet(pos, &coord); + if (coord.isReal()) { + ret = coord.getReal(); + } + coord.free(); +} + +/* return true if the point falls within the annotation rectangle */ +GBool Annot::selectAnnotation(double ux, double uy) { + int i, j; + + if (!inkList.isArray()) { + return gFalse; + } + // scan the array and see if the point we clicked is close to any + // segment connecting two points of the annotation + int ilength = inkList.arrayGetLength(); + + for (i = 0; i < ilength; i++) { + Object array; + if (!inkList.arrayGet(i, &array)->isArray()) + continue; + + int alength = array.arrayGetLength(); + double ox = -1, oy = -1; + for (j = 0; j < alength; j += 2) { + if (ox == -1 || oy == -1) { // first point + get_coord(array, j, ox); + get_coord(array, j + 1, oy); + continue; + } + double cx = ox, cy = oy; // default + get_coord(array, j, cx); + get_coord(array, j + 1, cy); + if (ox == cx && oy == cy) // same point, go on + continue; + // below, compute a rectangle around the segment and see if the + // point falls into the rectangle. + double dx = cx - ox; + double dy = cy - oy; + double l = sqrt(dx*dx + dy*dy); + double x = ((ux - ox)*dx + (uy - oy)*dy)/l; + double y = (-(ux - ox)*dy + (uy - oy)*dx)/l; + + if (x >= -2 && x<= l + 2 && y >= -4 && y <= 4) + return gTrue; + ox = cx; + oy = cy; + } + } + return gFalse; +} + // Set the current fill or stroke color, based on (which should // have 1, 3, or 4 elements). If is +1, color is brightened; // if is -1, color is darkened; otherwise color is not Index: xpdf/Dict.cc =================================================================== --- xpdf/Dict.cc (revision 1) +++ xpdf/Dict.cc (working copy) @@ -78,7 +78,6 @@ Object *Dict::lookupNF(char *key, Object *obj) { DictEntry *e; - return (e = find(key)) ? e->val.copy(obj) : obj->initNull(); } Index: CHANGES =================================================================== --- CHANGES (revision 1) +++ CHANGES (working copy) @@ -1760,3 +1760,65 @@ Allow comments in PostScript-type functions. Change the TrueType font parser (FoFiTrueType) to delete glyf table entries that are too short. + +3.02-annot (2008-jul-15) +------------------------ +This patch implements editable annotations (currently +only freehand drawing) for xpdf. + +An annotation requires three components to be added +to the pdf file: + + the inklist, which contains point coordinates meant to + be used at 'selection' time; + the 'stream' contains the points to be plotted + the xref contains references to the objects + +The code in this patch +./xpdf/Annot.cc +./xpdf/Annot.h + code to support inklist and selection + +./xpdf/GlobalParams.cc +./xpdf/GlobalParams.h + new bindings for the keys, basically mouse release and delete + +./xpdf/PDFCore.cc +./xpdf/PDFCore.h + + code to create the various pieces of the annotation. + There are three strings used to hold the 'annotation', + the stream and the 'appreareance' object, which are built + as the user moves the mouse with the left key pressed. + +./xpdf/PDFDoc.cc +./xpdf/PDFDoc.h + Code to manipulate the document. + addTempAnnotation() adds the pieces of a new annotation + to an existing file, making a temporary copy if necessary; + + delTempAnnotation() removes an annotation, for simplicity + it does so by overwriting the blocks with whitespace so that + the xref does not change. + +./xpdf/Page.cc +./xpdf/Page.h + some bits to support selection + +./xpdf/XPDFCore.cc +./xpdf/XPDFCore.h + creation of the annotation, similar to PDFCore.cc + Also implement a dialog box. + +./xpdf/XPDFViewer.cc +./xpdf/XPDFViewer.h + various GUI items, such as the box to select color and width, + and hooks to call the 'save' option when exiting. + +./xpdf/XRef.cc +./xpdf/XRef.h + support for a 'numObjects' variable which may or may not differ + from 'size' + +./xpdf/about-text.h + menus