//========================================================================
//
// Annot.cc
//
// Copyright 2000-2003 Glyph & Cog, LLC
//
//========================================================================

//========================================================================
//
// Modified under the Poppler project - http://poppler.freedesktop.org
//
// All changes made under the Poppler project to this file are licensed
// under GPL version 2 or later
//
// Copyright (C) 2006 Scott Turner <scotty1024@mac.com>
// Copyright (C) 2007, 2008 Julien Rebetez <julienr@svn.gnome.org>
// Copyright (C) 2007-2013, 2015-2017 Albert Astals Cid <aacid@kde.org>
// Copyright (C) 2007-2013 Carlos Garcia Campos <carlosgc@gnome.org>
// Copyright (C) 2007, 2008 Iñigo Martínez <inigomartinez@gmail.com>
// Copyright (C) 2007 Jeff Muizelaar <jeff@infidigm.net>
// Copyright (C) 2008, 2011 Pino Toscano <pino@kde.org>
// Copyright (C) 2008 Michael Vrable <mvrable@cs.ucsd.edu>
// Copyright (C) 2008 Hugo Mercier <hmercier31@gmail.com>
// Copyright (C) 2009 Ilya Gorenbein <igorenbein@finjan.com>
// Copyright (C) 2011, 2013 José Aliste <jaliste@src.gnome.org>
// Copyright (C) 2012, 2013 Fabio D'Urso <fabiodurso@hotmail.it>
// Copyright (C) 2012, 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
// Copyright (C) 2012, 2015 Tobias Koenig <tokoe@kdab.com>
// Copyright (C) 2013 Peter Breitenlohner <peb@mppmu.mpg.de>
// Copyright (C) 2013 Adrian Johnson <ajohnson@redneon.com>
// Copyright (C) 2014, 2015 Marek Kasik <mkasik@redhat.com>
// Copyright (C) 2014 Jiri Slaby <jirislaby@gmail.com>
// Copyright (C) 2014 Anuj Khare <khareanuj18@gmail.com>
// Copyright (C) 2015 Petr Gajdos <pgajdos@suse.cz>
// Copyright (C) 2015 Philipp Reinkemeier <philipp.reinkemeier@offis.de>
// Copyright (C) 2015 Tamas Szekeres <szekerest@gmail.com>
//
// To see a description of the changes please see the Changelog file that
// came with your tarball or type make ChangeLog if you are building from git
//
//========================================================================

#include <config.h>

#ifdef USE_GCC_PRAGMAS
#pragma implementation
#endif

#include <stdlib.h>
#include <math.h>
#include <assert.h>
#include "goo/gmem.h"
#include "goo/gstrtod.h"
#include "GooList.h"
#include "Error.h"
#include "Object.h"
#include "Catalog.h"
#include "Gfx.h"
#include "Lexer.h"
#include "PDFDoc.h"
#include "Page.h"
#include "Annot.h"
#include "GfxFont.h"
#include "CharCodeToUnicode.h"
#include "PDFDocEncoding.h"
#include "Form.h"
#include "Error.h"
#include "XRef.h"
#include "Movie.h"
#include "OptionalContent.h"
#include "Sound.h"
#include "FileSpec.h"
#include "DateInfo.h"
#include "Link.h"
#include <string.h>
#include <algorithm>

#if MULTITHREADED
#  define annotLocker()   MutexLocker locker(&mutex)
#  define annotCondLocker(X)  MutexLocker locker(&mutex, (X))
#else
#  define annotLocker()
#  define annotCondLocker(X)
#endif

#define fieldFlagReadOnly           0x00000001
#define fieldFlagRequired           0x00000002
#define fieldFlagNoExport           0x00000004
#define fieldFlagMultiline          0x00001000
#define fieldFlagPassword           0x00002000
#define fieldFlagNoToggleToOff      0x00004000
#define fieldFlagRadio              0x00008000
#define fieldFlagPushbutton         0x00010000
#define fieldFlagCombo              0x00020000
#define fieldFlagEdit               0x00040000
#define fieldFlagSort               0x00080000
#define fieldFlagFileSelect         0x00100000
#define fieldFlagMultiSelect        0x00200000
#define fieldFlagDoNotSpellCheck    0x00400000
#define fieldFlagDoNotScroll        0x00800000
#define fieldFlagComb               0x01000000
#define fieldFlagRichText           0x02000000
#define fieldFlagRadiosInUnison     0x02000000
#define fieldFlagCommitOnSelChange  0x04000000

#define fieldQuadLeft   0
#define fieldQuadCenter 1
#define fieldQuadRight  2

// distance of Bezier control point from center for circle approximation
// = (4 * (sqrt(2) - 1) / 3) * r
#define bezierCircle 0.55228475

AnnotLineEndingStyle parseAnnotLineEndingStyle(GooString *string) {
  if (string != NULL) {
    if (!string->cmp("Square")) {
      return annotLineEndingSquare;
    } else if (!string->cmp("Circle")) {
      return annotLineEndingCircle;
    } else if (!string->cmp("Diamond")) {
      return annotLineEndingDiamond;
    } else if (!string->cmp("OpenArrow")) {
      return annotLineEndingOpenArrow;
    } else if (!string->cmp("ClosedArrow")) {
      return annotLineEndingClosedArrow;
    } else if (!string->cmp("Butt")) {
      return annotLineEndingButt;
    } else if (!string->cmp("ROpenArrow")) {
      return annotLineEndingROpenArrow;
    } else if (!string->cmp("RClosedArrow")) {
      return annotLineEndingRClosedArrow;
    } else if (!string->cmp("Slash")) {
      return annotLineEndingSlash;
    } else {
      return annotLineEndingNone;
    }
  } else {
    return annotLineEndingNone;
  }  
}

const char* convertAnnotLineEndingStyle(AnnotLineEndingStyle style) {
  switch (style) {
    case annotLineEndingSquare:
      return "Square";
    case annotLineEndingCircle:
      return "Circle";
    case annotLineEndingDiamond:
      return "Diamond";
    case annotLineEndingOpenArrow:
      return "OpenArrow";
    case annotLineEndingClosedArrow:
      return "ClosedArrow";
    case annotLineEndingButt:
      return "Butt";
    case annotLineEndingROpenArrow:
      return "ROpenArrow";
    case annotLineEndingRClosedArrow:
      return "RClosedArrow";
    case annotLineEndingSlash:
      return "Slash";
    default:
      return "None";
  }
}

static AnnotExternalDataType parseAnnotExternalData(Dict* dict) {
  Object obj1;
  AnnotExternalDataType type;

  if (dict->lookup("Subtype", &obj1)->isName()) {
    const char *typeName = obj1.getName();

    if (!strcmp(typeName, "Markup3D")) {
      type = annotExternalDataMarkup3D;
    } else {
      type = annotExternalDataMarkupUnknown;
    }
  } else {
    type = annotExternalDataMarkupUnknown;
  }
  obj1.free();

  return type;
}

PDFRectangle *parseDiffRectangle(Array *array, PDFRectangle *rect) {
  PDFRectangle *newRect = NULL;
  if (array->getLength() == 4) {
    // deltas
    Object obj1;
    double dx1 = (array->get(0, &obj1)->isNum() ? obj1.getNum() : 0);
    obj1.free();
    double dy1 = (array->get(1, &obj1)->isNum() ? obj1.getNum() : 0);
    obj1.free();
    double dx2 = (array->get(2, &obj1)->isNum() ? obj1.getNum() : 0);
    obj1.free();
    double dy2 = (array->get(3, &obj1)->isNum() ? obj1.getNum() : 0);
    obj1.free();

    // checking that the numbers are valid (i.e. >= 0),
    // and that applying the differences still give us a valid rect
    if (dx1 >= 0 && dy1 >= 0 && dx2 >= 0 && dy2
        && (rect->x2 - rect->x1 - dx1 - dx2) >= 0
        && (rect->y2 - rect->y1 - dy1 - dy2) >= 0) {
      newRect = new PDFRectangle();
      newRect->x1 = rect->x1 + dx1;
      newRect->y1 = rect->y1 + dy1;
      newRect->x2 = rect->x2 - dx2;
      newRect->y2 = rect->y2 - dy2;
    }
  }
  return newRect;
}

static LinkAction* getAdditionalAction(Annot::AdditionalActionsType type, Object *additionalActions, PDFDoc *doc) {
  Object additionalActionsObject;
  LinkAction *linkAction = NULL;

  if (additionalActions->fetch(doc->getXRef(), &additionalActionsObject)->isDict()) {
    const char *key = (type == Annot::actionCursorEntering ? "E" :
                       type == Annot::actionCursorLeaving ?  "X" :
                       type == Annot::actionMousePressed ?   "D" :
                       type == Annot::actionMouseReleased ?  "U" :
                       type == Annot::actionFocusIn ?       "Fo" :
                       type == Annot::actionFocusOut ?      "BI" :
                       type == Annot::actionPageOpening ?   "PO" :
                       type == Annot::actionPageClosing ?   "PC" :
                       type == Annot::actionPageVisible ?   "PV" :
                       type == Annot::actionPageInvisible ? "PI" : NULL);

    Object actionObject;

    if (additionalActionsObject.dictLookup(key, &actionObject)->isDict())
      linkAction = LinkAction::parseAction(&actionObject, doc->getCatalog()->getBaseURI());
    actionObject.free();
  }

  additionalActionsObject.free();

  return linkAction;
}

static LinkAction* getFormAdditionalAction(Annot::FormAdditionalActionsType type, Object *additionalActions, PDFDoc *doc) {
  Object additionalActionsObject;
  LinkAction *linkAction = NULL;

  if (additionalActions->fetch(doc->getXRef(), &additionalActionsObject)->isDict()) {
    const char *key = (type == Annot::actionFieldModified ?  "K" :
                       type == Annot::actionFormatField ?    "F" :
                       type == Annot::actionValidateField ?  "V" :
                       type == Annot::actionCalculateField ? "C" : NULL);

    Object actionObject;

    if (additionalActionsObject.dictLookup(key, &actionObject)->isDict())
      linkAction = LinkAction::parseAction(&actionObject, doc->getCatalog()->getBaseURI());
    actionObject.free();
  }

  additionalActionsObject.free();

  return linkAction;
}

//------------------------------------------------------------------------
// AnnotBorderEffect
//------------------------------------------------------------------------

AnnotBorderEffect::AnnotBorderEffect(Dict *dict) {
  Object obj1;

  if (dict->lookup("S", &obj1)->isName()) {
    const char *effectName = obj1.getName();

    if (!strcmp(effectName, "C"))
      effectType = borderEffectCloudy;
    else
      effectType = borderEffectNoEffect;
  } else {
    effectType = borderEffectNoEffect;
  }
  obj1.free();

  if ((dict->lookup("I", &obj1)->isNum()) && effectType == borderEffectCloudy) {
    intensity = obj1.getNum();
  } else {
    intensity = 0;
  }
  obj1.free();
}

//------------------------------------------------------------------------
// AnnotPath
//------------------------------------------------------------------------

AnnotPath::AnnotPath() {
  coords = NULL;
  coordsLength = 0;
}

AnnotPath::AnnotPath(Array *array) {
  coords = NULL;
  coordsLength = 0;
  parsePathArray(array);
}

AnnotPath::AnnotPath(AnnotCoord **coords, int coordsLength) {
  this->coords = coords;
  this->coordsLength = coordsLength;
}

AnnotPath::~AnnotPath() {
  if (coords) {
    for (int i = 0; i < coordsLength; ++i)
      delete coords[i];
    gfree(coords);
  }
}

double AnnotPath::getX(int coord) const {
  if (coord >= 0 && coord < coordsLength)
    return coords[coord]->getX();
  return 0;
}

double AnnotPath::getY(int coord) const {
  if (coord >= 0 && coord < coordsLength)
    return coords[coord]->getY();
  return 0;
}

AnnotCoord *AnnotPath::getCoord(int coord) const {
  if (coord >= 0 && coord < coordsLength)
    return coords[coord];
  return NULL;
}

void AnnotPath::parsePathArray(Array *array) {
  int tempLength;
  AnnotCoord **tempCoords;
  GBool correct = gTrue;

  if (array->getLength() % 2) {
    error(errSyntaxError, -1, "Bad Annot Path");
    return;
  }

  tempLength = array->getLength() / 2;
  tempCoords = (AnnotCoord **) gmallocn (tempLength, sizeof(AnnotCoord *));
  memset(tempCoords, 0, tempLength * sizeof(AnnotCoord *));
  for (int i = 0; i < tempLength && correct; i++) {
    Object obj1;
    double x = 0, y = 0;

    if (array->get(i * 2, &obj1)->isNum()) {
      x = obj1.getNum();
    } else {
      correct = gFalse;
    }
    obj1.free();

    if (array->get((i * 2) + 1, &obj1)->isNum()) {
      y = obj1.getNum();
    } else {
      correct = gFalse;
    }
    obj1.free();

    if (!correct) {
      for (int j = i - 1; j >= 0; j--)
        delete tempCoords[j];
      gfree (tempCoords);
      return;
    }

    tempCoords[i] = new AnnotCoord(x, y);
  }

  coords = tempCoords;
  coordsLength = tempLength;
}

//------------------------------------------------------------------------
// AnnotCalloutLine
//------------------------------------------------------------------------

AnnotCalloutLine::AnnotCalloutLine(double x1, double y1, double x2, double y2)
    :  coord1(x1, y1), coord2(x2, y2) {
}

//------------------------------------------------------------------------
// AnnotCalloutMultiLine
//------------------------------------------------------------------------

AnnotCalloutMultiLine::AnnotCalloutMultiLine(double x1, double y1, double x2,
                                             double y2, double x3, double y3)
    : AnnotCalloutLine(x1, y1, x2, y2), coord3(x3, y3) {
}

//------------------------------------------------------------------------
// AnnotQuadrilateral
//------------------------------------------------------------------------

AnnotQuadrilaterals::AnnotQuadrilaterals(Array *array, PDFRectangle *rect) {
  int arrayLength = array->getLength();
  GBool correct = gTrue;
  int quadsLength = 0;
  AnnotQuadrilateral **quads;
  double quadArray[8];

  // default values
  quadrilaterals = NULL;
  quadrilateralsLength = 0;

  if ((arrayLength % 8) == 0) {
    int i;

    quadsLength = arrayLength / 8;
    quads = (AnnotQuadrilateral **) gmallocn
        ((quadsLength), sizeof(AnnotQuadrilateral *));
    memset(quads, 0, quadsLength * sizeof(AnnotQuadrilateral *));

    for (i = 0; i < quadsLength; i++) {
      for (int j = 0; j < 8; j++) {
        Object obj;
        if (array->get(i * 8 + j, &obj)->isNum()) {
          quadArray[j] = obj.getNum();
        } else {
            correct = gFalse;
	    obj.free();
	    error (errSyntaxError, -1, "Invalid QuadPoint in annot");
	    break;
        }
        obj.free();
      }

      if (!correct)
        break;

      quads[i] = new AnnotQuadrilateral(quadArray[0], quadArray[1],
					quadArray[2], quadArray[3],
					quadArray[4], quadArray[5],
					quadArray[6], quadArray[7]);
    }

    if (correct) {
      quadrilateralsLength = quadsLength;
      quadrilaterals = quads;
    } else {
      for (int j = 0; j < i; j++)
        delete quads[j];
      gfree (quads);
    }
  }
}

AnnotQuadrilaterals::AnnotQuadrilaterals(AnnotQuadrilaterals::AnnotQuadrilateral **quads, int quadsLength) {
  quadrilaterals = quads;
  quadrilateralsLength = quadsLength;
}

AnnotQuadrilaterals::~AnnotQuadrilaterals() {
  if (quadrilaterals) {
    for(int i = 0; i < quadrilateralsLength; i++)
      delete quadrilaterals[i];

    gfree (quadrilaterals);
  }
}

double AnnotQuadrilaterals::getX1(int quadrilateral) {
  if (quadrilateral >= 0  && quadrilateral < quadrilateralsLength)
    return quadrilaterals[quadrilateral]->coord1.getX();
  return 0;
}

double AnnotQuadrilaterals::getY1(int quadrilateral) {
  if (quadrilateral >= 0  && quadrilateral < quadrilateralsLength)
    return quadrilaterals[quadrilateral]->coord1.getY();
  return 0;
}

double AnnotQuadrilaterals::getX2(int quadrilateral) {
  if (quadrilateral >= 0  && quadrilateral < quadrilateralsLength)
    return quadrilaterals[quadrilateral]->coord2.getX();
  return 0;
}

double AnnotQuadrilaterals::getY2(int quadrilateral) {
  if (quadrilateral >= 0  && quadrilateral < quadrilateralsLength)
    return quadrilaterals[quadrilateral]->coord2.getY();
  return 0;
}

double AnnotQuadrilaterals::getX3(int quadrilateral) {
  if (quadrilateral >= 0  && quadrilateral < quadrilateralsLength)
    return quadrilaterals[quadrilateral]->coord3.getX();
  return 0;
}

double AnnotQuadrilaterals::getY3(int quadrilateral) {
  if (quadrilateral >= 0  && quadrilateral < quadrilateralsLength)
    return quadrilaterals[quadrilateral]->coord3.getY();
  return 0;
}

double AnnotQuadrilaterals::getX4(int quadrilateral) {
  if (quadrilateral >= 0  && quadrilateral < quadrilateralsLength)
    return quadrilaterals[quadrilateral]->coord4.getX();
  return 0;
}

double AnnotQuadrilaterals::getY4(int quadrilateral) {
  if (quadrilateral >= 0  && quadrilateral < quadrilateralsLength)
    return quadrilaterals[quadrilateral]->coord4.getY();
  return 0;
}

AnnotQuadrilaterals::AnnotQuadrilateral::AnnotQuadrilateral(double x1, double y1,
    double x2, double y2, double x3, double y3, double x4, double y4)
    : coord1(x1, y1), coord2(x2, y2), coord3(x3, y3), coord4(x4, y4) {
}

//------------------------------------------------------------------------
// AnnotBorder
//------------------------------------------------------------------------
AnnotBorder::AnnotBorder() {
  width = 1;
  dashLength = 0;
  dash = NULL;
  style = borderSolid;
}

GBool AnnotBorder::parseDashArray(Object *dashObj) {
  GBool correct = gTrue;
  int tempLength = dashObj->arrayGetLength();
  double *tempDash = (double *) gmallocn (tempLength, sizeof (double));

  // TODO: check not all zero (Line Dash Pattern Page 217 PDF 8.1)
  for (int i = 0; i < tempLength && i < DASH_LIMIT && correct; i++) {
    Object obj1;

    if (dashObj->arrayGet(i, &obj1)->isNum()) {
      tempDash[i] = obj1.getNum();

      correct = tempDash[i] >= 0;
      obj1.free();
    }
  }

  if (correct) {
    dashLength = tempLength;
    dash = tempDash;
    style = borderDashed;
  } else {
    gfree (tempDash);
  }

  return correct;
}

AnnotBorder::~AnnotBorder() {
  if (dash)
    gfree (dash); 
}
  
//------------------------------------------------------------------------
// AnnotBorderArray
//------------------------------------------------------------------------

AnnotBorderArray::AnnotBorderArray() {
  horizontalCorner = 0;
  verticalCorner = 0;
}

AnnotBorderArray::AnnotBorderArray(Array *array) {
  Object obj1;
  int arrayLength = array->getLength();

  GBool correct = gTrue;
  if (arrayLength == 3 || arrayLength == 4) {
    // implementation note 81 in Appendix H.

    if (array->get(0, &obj1)->isNum())
      horizontalCorner = obj1.getNum();
    else
      correct = gFalse;
    obj1.free();

    if (array->get(1, &obj1)->isNum())
      verticalCorner = obj1.getNum();
    else
      correct = gFalse;
    obj1.free();

    if (array->get(2, &obj1)->isNum())
      width = obj1.getNum();
    else
      correct = gFalse;
    obj1.free();

    if (arrayLength == 4) {
      if (array->get(3, &obj1)->isArray())
        correct = parseDashArray(&obj1);
      else
        correct = gFalse;
      obj1.free();
    }
  } else {
    correct = gFalse;
  }
  
  if (!correct) {
    width = 0;
  }
}

void AnnotBorderArray::writeToObject(XRef *xref, Object *obj1) const {
  Object obj2;

  obj1->initArray(xref);
  obj1->arrayAdd(obj2.initReal(horizontalCorner));
  obj1->arrayAdd(obj2.initReal(verticalCorner));
  obj1->arrayAdd(obj2.initReal(width));

  if (dashLength > 0) {
    Object obj3;

    obj1->arrayAdd(obj3.initArray(xref));
    for (int i = 0; i < dashLength; i++)
      obj3.arrayAdd(obj2.initReal(dash[i]));
  }
}

//------------------------------------------------------------------------
// AnnotBorderBS
//------------------------------------------------------------------------

AnnotBorderBS::AnnotBorderBS() {
}

AnnotBorderBS::AnnotBorderBS(Dict *dict) {
  Object obj1, obj2;

  // acroread 8 seems to need both W and S entries for
  // any border to be drawn, even though the spec
  // doesn't claim anything of that sort. We follow
  // that behaviour by veryifying both entries exist
  // otherwise we set the borderWidth to 0
  // --jrmuizel
  dict->lookup("W", &obj1);
  dict->lookup("S", &obj2);
  if (obj1.isNum() && obj2.isName()) {
    const char *styleName = obj2.getName();

    width = obj1.getNum();

    if (!strcmp(styleName, "S")) {
      style = borderSolid;
    } else if (!strcmp(styleName, "D")) {
      style = borderDashed;
    } else if (!strcmp(styleName, "B")) {
      style = borderBeveled;
    } else if (!strcmp(styleName, "I")) {
      style = borderInset;
    } else if (!strcmp(styleName, "U")) {
      style = borderUnderlined;
    } else {
      style = borderSolid;
    }
  } else {
    width = 0;
  }
  obj2.free();
  obj1.free();

  if (style == borderDashed) {
    if (dict->lookup("D", &obj1)->isArray())
      parseDashArray(&obj1);
    obj1.free();

    if (!dash) {
      dashLength = 1;
      dash = (double *) gmallocn (dashLength, sizeof (double));
      dash[0] = 3;
    }
  }
}

const char *AnnotBorderBS::getStyleName() const {
  switch (style) {
  case borderSolid:
    return "S";
  case borderDashed:
    return "D";
  case borderBeveled:
    return "B";
  case borderInset:
    return "I";
  case borderUnderlined:
    return "U";
  }

  return "S";
}

void AnnotBorderBS::writeToObject(XRef *xref, Object *obj1) const {
  Object obj2;

  obj1->initDict(xref);
  obj1->dictSet("W", obj2.initReal(width));
  obj1->dictSet("S", obj2.initName(getStyleName()));
  if (style == borderDashed && dashLength > 0) {
    Object obj3;

    obj1->dictSet("D", obj3.initArray(xref));
    for (int i = 0; i < dashLength; i++)
      obj3.arrayAdd(obj2.initReal(dash[i]));
  }
}

//------------------------------------------------------------------------
// AnnotColor
//------------------------------------------------------------------------

AnnotColor::AnnotColor() {
  length = 0;
}

AnnotColor::AnnotColor(double gray) {
  length = 1;

  values[0] = gray;
}

AnnotColor::AnnotColor(double r, double g, double b) {
  length = 3;

  values[0] = r;
  values[1] = g;
  values[2] = b;
}

AnnotColor::AnnotColor(double c, double m, double y, double k) {
  length = 4;

  values[0] = c;
  values[1] = m;
  values[2] = y;
  values[3] = k;
}

// If <adjust> is +1, color is brightened;
// if <adjust> is -1, color is darkened;
// otherwise color is not modified.
AnnotColor::AnnotColor(Array *array, int adjust) {
  int i;

  length = array->getLength();
  if (length > 4)
    length = 4;

  for (i = 0; i < length; i++) {
    Object obj1;

    if (array->get(i, &obj1)->isNum()) {
      values[i] = obj1.getNum();

      if (values[i] < 0 || values[i] > 1)
        values[i] = 0;
    } else {
      values[i] = 0;
    }
    obj1.free();
  }

  if (adjust != 0)
    adjustColor(adjust);
}

void AnnotColor::adjustColor(int adjust) {
  int i;

  if (length == 4) {
    adjust = -adjust;
  }
  if (adjust > 0) {
    for (i = 0; i < length; ++i) {
      values[i] = 0.5 * values[i] + 0.5;
    }
  } else if (adjust < 0) {
    for (i = 0; i < length; ++i) {
      values[i] = 0.5 * values[i];
    }
  }
}

void AnnotColor::writeToObject(XRef *xref, Object *obj1) const {
  Object obj2;
  int i;

  if (length == 0) {
    obj1->initNull(); // Transparent (no color)
  } else {
    obj1->initArray(xref);
    for (i = 0; i < length; ++i)
      obj1->arrayAdd( obj2.initReal( values[i] ) );
  }
}

//------------------------------------------------------------------------
// AnnotIconFit
//------------------------------------------------------------------------

AnnotIconFit::AnnotIconFit(Dict* dict) {
  Object obj1;

  if (dict->lookup("SW", &obj1)->isName()) {
    const char *scaleName = obj1.getName();

    if(!strcmp(scaleName, "B")) {
      scaleWhen = scaleBigger;
    } else if(!strcmp(scaleName, "S")) {
      scaleWhen = scaleSmaller;
    } else if(!strcmp(scaleName, "N")) {
      scaleWhen = scaleNever;
    } else {
      scaleWhen = scaleAlways;
    }
  } else {
    scaleWhen = scaleAlways;
  }
  obj1.free();

  if (dict->lookup("S", &obj1)->isName()) {
    const char *scaleName = obj1.getName();

    if(!strcmp(scaleName, "A")) {
      scale = scaleAnamorphic;
    } else {
      scale = scaleProportional;
    }
  } else {
    scale = scaleProportional;
  }
  obj1.free();

  if (dict->lookup("A", &obj1)->isArray() && obj1.arrayGetLength() == 2) {
    Object obj2;
    (obj1.arrayGet(0, &obj2)->isNum() ? left = obj2.getNum() : left = 0);
    obj2.free();
    (obj1.arrayGet(1, &obj2)->isNum() ? bottom = obj2.getNum() : bottom = 0);
    obj2.free();

    if (left < 0 || left > 1)
      left = 0.5;

    if (bottom < 0 || bottom > 1)
      bottom = 0.5;

  } else {
    left = bottom = 0.5;
  }
  obj1.free();

  if (dict->lookup("FB", &obj1)->isBool()) {
    fullyBounds = obj1.getBool();
  } else {
    fullyBounds = gFalse;
  }
  obj1.free();
}

//------------------------------------------------------------------------
// AnnotAppearance
//------------------------------------------------------------------------

AnnotAppearance::AnnotAppearance(PDFDoc *docA, Object *dict) {
  assert(dict->isDict());
  doc = docA;
  xref = docA->getXRef();
  dict->copy(&appearDict);
}

AnnotAppearance::~AnnotAppearance() {
  appearDict.free();
}

void AnnotAppearance::getAppearanceStream(AnnotAppearanceType type, const char *state, Object *dest) {
  Object apData, stream;
  apData.initNull();

  // Obtain dictionary or stream associated to appearance type
  switch (type) {
  case appearRollover:
    if (appearDict.dictLookupNF("R", &apData)->isNull())
      appearDict.dictLookupNF("N", &apData);
    break;
  case appearDown:
    if (appearDict.dictLookupNF("D", &apData)->isNull())
      appearDict.dictLookupNF("N", &apData);
    break;
  case appearNormal:
    appearDict.dictLookupNF("N", &apData);
    break;
  }

  dest->initNull();
  if (apData.isDict() && state)
    apData.dictLookupNF(state, dest);
  else if (apData.isRef())
    apData.copy(dest);
  apData.free();
}

GooString * AnnotAppearance::getStateKey(int i) {
  Object obj1;
  GooString * res = NULL;
  if (appearDict.dictLookupNF("N", &obj1)->isDict())
    res = new GooString(obj1.dictGetKey(i));
  obj1.free();
  return res;
}

int AnnotAppearance::getNumStates() {
  Object obj1;
  int res = 0;
  if (appearDict.dictLookupNF("N", &obj1)->isDict())
    res = obj1.dictGetLength();
  obj1.free();
  return res;
}

// Test if stateObj (a Ref or a Dict) points to the specified stream
GBool AnnotAppearance::referencesStream(Object *stateObj, Ref refToStream) {
  if (stateObj->isRef()) {
    Ref r = stateObj->getRef();
    if (r.num == refToStream.num && r.gen == refToStream.gen) {
      return gTrue;
    }
  } else if (stateObj->isDict()) { // Test each value
    const int size = stateObj->dictGetLength();
    for (int i = 0; i < size; ++i) {
      Object obj1;
      stateObj->dictGetValNF(i, &obj1);
      if (obj1.isRef()) {
        Ref r = obj1.getRef();
        if (r.num == refToStream.num && r.gen == refToStream.gen) {
          return gTrue;
        }
      }
      obj1.free();
    }
  }
  return gFalse; // Not found
}

// Test if this AnnotAppearance references the specified stream
GBool AnnotAppearance::referencesStream(Ref refToStream) {
  Object obj1;
  GBool found;

  // Scan each state's ref/subdictionary
  appearDict.dictLookupNF("N", &obj1);
  found = referencesStream(&obj1, refToStream);
  obj1.free();
  if (found)
    return gTrue;

  appearDict.dictLookupNF("R", &obj1);
  found = referencesStream(&obj1, refToStream);
  obj1.free();
  if (found)
    return gTrue;

  appearDict.dictLookupNF("D", &obj1);
  found = referencesStream(&obj1, refToStream);
  obj1.free();
  return found;
}

// If this is the only annotation in the document that references the
// specified appearance stream, remove the appearance stream
void AnnotAppearance::removeStream(Ref refToStream) {
  const int lastpage = doc->getNumPages();
  for (int pg = 1; pg <= lastpage; ++pg) { // Scan all annotations in the document
    Page *page = doc->getPage(pg);
    if (!page) {
      error(errSyntaxError, -1, "Failed check for shared annotation stream at page {0:d}", pg);
      continue;
    }
    Annots *annots = page->getAnnots();
    for (int i = 0; i < annots->getNumAnnots(); ++i) {
      AnnotAppearance *annotAp = annots->getAnnot(i)->getAppearStreams();
      if (annotAp && annotAp != this && annotAp->referencesStream(refToStream)) {
        return; // Another annotation points to the stream -> Don't delete it
      }
    }
  }

  // TODO: stream resources (e.g. font), AP name tree
  xref->removeIndirectObject(refToStream);
}

// Removes stream if obj is a Ref, or removes pointed streams if obj is a Dict
void AnnotAppearance::removeStateStreams(Object *obj1) {
  if (obj1->isRef()) {
    removeStream(obj1->getRef());
  } else if (obj1->isDict()) {
    const int size = obj1->dictGetLength();
    for (int i = 0; i < size; ++i) {
      Object obj2;
      obj1->dictGetValNF(i, &obj2);
      if (obj2.isRef()) {
        removeStream(obj2.getRef());
      }
      obj2.free();
    }
  }
}

void AnnotAppearance::removeAllStreams() {
  Object obj1;
  appearDict.dictLookupNF("N", &obj1);
  removeStateStreams(&obj1);
  obj1.free();
  appearDict.dictLookupNF("R", &obj1);
  removeStateStreams(&obj1);
  obj1.free();
  appearDict.dictLookupNF("D", &obj1);
  removeStateStreams(&obj1);
  obj1.free();
}

//------------------------------------------------------------------------
// AnnotAppearanceCharacs
//------------------------------------------------------------------------

AnnotAppearanceCharacs::AnnotAppearanceCharacs(Dict *dict) {
  Object obj1;

  if (dict->lookup("R", &obj1)->isInt()) {
    rotation = obj1.getInt();
  } else {
    rotation = 0;
  }
  obj1.free();

  if (dict->lookup("BC", &obj1)->isArray()) {
    Array *colorComponents = obj1.getArray();
    if (colorComponents->getLength() > 0) {
      borderColor = new AnnotColor(colorComponents);
    } else {
      borderColor = NULL;
    }
  } else {
    borderColor = NULL;
  }
  obj1.free();

  if (dict->lookup("BG", &obj1)->isArray()) {
    Array *colorComponents = obj1.getArray();
    if (colorComponents->getLength() > 0) {
      backColor = new AnnotColor(colorComponents);
    } else {
      backColor = NULL;
    }
  } else {
    backColor = NULL;
  }
  obj1.free();

  if (dict->lookup("CA", &obj1)->isString()) {
    normalCaption = new GooString(obj1.getString());
  } else {
    normalCaption = NULL;
  }
  obj1.free();

  if (dict->lookup("RC", &obj1)->isString()) {
    rolloverCaption = new GooString(obj1.getString());
  } else {
    rolloverCaption = NULL;
  }
  obj1.free();

  if (dict->lookup("AC", &obj1)->isString()) {
    alternateCaption = new GooString(obj1.getString());
  } else {
    alternateCaption = NULL;
  }
  obj1.free();

  if (dict->lookup("IF", &obj1)->isDict()) {
    iconFit = new AnnotIconFit(obj1.getDict());
  } else {
    iconFit = NULL;
  }
  obj1.free();

  if (dict->lookup("TP", &obj1)->isInt()) {
    position = (AnnotAppearanceCharacsTextPos) obj1.getInt();
  } else {
    position = captionNoIcon;
  }
  obj1.free();
}

AnnotAppearanceCharacs::~AnnotAppearanceCharacs() {
  if (borderColor)
    delete borderColor;

  if (backColor)
    delete backColor;

  if (normalCaption)
    delete normalCaption;

  if (rolloverCaption)
    delete rolloverCaption;

  if (alternateCaption)
    delete alternateCaption;

  if (iconFit)
    delete iconFit;
}

//------------------------------------------------------------------------
// AnnotAppearanceBBox
//------------------------------------------------------------------------

AnnotAppearanceBBox::AnnotAppearanceBBox(PDFRectangle *rect) {
  origX = rect->x1;
  origY = rect->y1;
  borderWidth = 0;

  // Initially set the same size as rect
  minX = 0;
  minY = 0;
  maxX = rect->x2 - rect->x1;
  maxY = rect->y2 - rect->y1;
}

void AnnotAppearanceBBox::extendTo(double x, double y) {
  if (x < minX) {
    minX = x;
  } else if (x > maxX) {
    maxX = x;
  }
  if (y < minY) {
    minY = y;
  } else if (y > maxY) {
    maxY = y;
  }
}

void AnnotAppearanceBBox::getBBoxRect(double bbox[4]) const {
  bbox[0] = minX - borderWidth;
  bbox[1] = minY - borderWidth;
  bbox[2] = maxX + borderWidth;
  bbox[3] = maxY + borderWidth;
}

double AnnotAppearanceBBox::getPageXMin() const {
  return origX + minX - borderWidth;
}

double AnnotAppearanceBBox::getPageYMin() const {
  return origY + minY - borderWidth;
}

double AnnotAppearanceBBox::getPageXMax() const {
  return origX + maxX + borderWidth;
}

double AnnotAppearanceBBox::getPageYMax() const {
  return origY + maxY + borderWidth;
}

//------------------------------------------------------------------------
// Annot
//------------------------------------------------------------------------

Annot::Annot(PDFDoc *docA, PDFRectangle *rectA) {
  Object obj1;

  refCnt = 1;
  flags = flagUnknown;
  type = typeUnknown;

  obj1.initArray (docA->getXRef());
  Object obj2;
  obj1.arrayAdd (obj2.initReal (rectA->x1));
  obj1.arrayAdd (obj2.initReal (rectA->y1));
  obj1.arrayAdd (obj2.initReal (rectA->x2));
  obj1.arrayAdd (obj2.initReal (rectA->y2));
  obj2.free ();

  annotObj.initDict (docA->getXRef());
  annotObj.dictSet ("Type", obj2.initName ("Annot"));
  annotObj.dictSet ("Rect", &obj1);
  // obj1 is owned by the dict

  ref = docA->getXRef()->addIndirectObject (&annotObj);

  initialize (docA, annotObj.getDict());
}

Annot::Annot(PDFDoc *docA, Dict *dict) {
  refCnt = 1;
  hasRef = false;
  flags = flagUnknown;
  type = typeUnknown;
  annotObj.initDict (dict);
  initialize (docA, dict);
}

Annot::Annot(PDFDoc *docA, Dict *dict, Object *obj) {
  refCnt = 1;
  if (obj->isRef()) {
    hasRef = gTrue;
    ref = obj->getRef();
  } else {
    hasRef = gFalse;
  }
  flags = flagUnknown;
  type = typeUnknown;
  annotObj.initDict (dict);
  initialize (docA, dict);
}

void Annot::initialize(PDFDoc *docA, Dict *dict) {
  Object apObj, asObj, obj1, obj2;

  ok = gTrue;
  doc = docA;
  xref = doc->getXRef();
  appearStreams = NULL;
  appearBBox = NULL;
  appearState = NULL;
  appearBuf = NULL;
  fontSize = 0;

  appearance.initNull();

  //----- parse the rectangle
  rect = new PDFRectangle();
  if (dict->lookup("Rect", &obj1)->isArray() && obj1.arrayGetLength() == 4) {
    Object obj2;
    (obj1.arrayGet(0, &obj2)->isNum() ? rect->x1 = obj2.getNum() : rect->x1 = 0);
    obj2.free();
    (obj1.arrayGet(1, &obj2)->isNum() ? rect->y1 = obj2.getNum() : rect->y1 = 0);
    obj2.free();
    (obj1.arrayGet(2, &obj2)->isNum() ? rect->x2 = obj2.getNum() : rect->x2 = 1);
    obj2.free();
    (obj1.arrayGet(3, &obj2)->isNum() ? rect->y2 = obj2.getNum() : rect->y2 = 1);
    obj2.free();

    if (rect->x1 > rect->x2) {
      double t = rect->x1;
      rect->x1 = rect->x2;
      rect->x2 = t;
    }

    if (rect->y1 > rect->y2) {
      double t = rect->y1;
      rect->y1 = rect->y2;
      rect->y2 = t;
    }
  } else {
    rect->x1 = rect->y1 = 0;
    rect->x2 = rect->y2 = 1;
    error(errSyntaxError, -1, "Bad bounding box for annotation");
    ok = gFalse;
  }
  obj1.free();

  if (dict->lookup("Contents", &obj1)->isString()) {
    contents = obj1.getString()->copy();
  } else {
    contents = new GooString();
  }
  obj1.free();

  // Note: This value is overwritten by Annots ctor
  if (dict->lookupNF("P", &obj1)->isRef()) {
    Ref ref = obj1.getRef();

    page = doc->getCatalog()->findPage (ref.num, ref.gen);
  } else {
    page = 0;
  }
  obj1.free();

  if (dict->lookup("NM", &obj1)->isString()) {
    name = obj1.getString()->copy();
  } else {
    name = NULL;
  }
  obj1.free();

  if (dict->lookup("M", &obj1)->isString()) {
    modified = obj1.getString()->copy();
  } else {
    modified = NULL;
  }
  obj1.free();

  //----- get the flags
  if (dict->lookup("F", &obj1)->isInt()) {
    flags |= obj1.getInt();
  } else {
    flags = flagUnknown;
  }
  obj1.free();

  //----- get the annotation appearance dictionary
  dict->lookup("AP", &apObj);
  if (apObj.isDict()) {
    appearStreams = new AnnotAppearance(doc, &apObj);
  }
  apObj.free();

  //----- get the appearance state
  dict->lookup("AS", &asObj);
  if (asObj.isName()) {
    appearState = new GooString(asObj.getName());
  } else if (appearStreams && appearStreams->getNumStates() != 0) {
    error (errSyntaxError, -1, "Invalid or missing AS value in annotation containing one or more appearance subdictionaries");
    // AS value is required in this case, but if the
    // N dictionary contains only one entry
    // take it as default appearance.
    if (appearStreams->getNumStates() == 1) {
      appearState = appearStreams->getStateKey(0);
    }
  }
  if (!appearState) {
    appearState = new GooString("Off");
  }
  asObj.free();

  //----- get the annotation appearance
  if (appearStreams) {
    appearStreams->getAppearanceStream(AnnotAppearance::appearNormal, appearState->getCString(), &appearance);
  }

  //----- parse the border style
  // According to the spec if neither the Border nor the BS entry is present,
  // the border shall be drawn as a solid line with a width of 1 point. But acroread
  // seems to ignore the Border entry for annots that can't have a BS entry. So, we only
  // follow this rule for annots tha can have a BS entry.
  if (dict->lookup("Border", &obj1)->isArray())
    border = new AnnotBorderArray(obj1.getArray());
  else
    border = NULL;
  obj1.free();

  if (dict->lookup("C", &obj1)->isArray()) {
    color = new AnnotColor(obj1.getArray());
  } else {
    color = NULL;
  }
  obj1.free();

  if (dict->lookup("StructParent", &obj1)->isInt()) {
    treeKey = obj1.getInt();
  } else {
    treeKey = 0;
  }
  obj1.free();

  dict->lookupNF("OC", &oc);

#if MULTITHREADED
  gInitMutex(&mutex);
#endif
}

void Annot::getRect(double *x1, double *y1, double *x2, double *y2) const {
  *x1 = rect->x1;
  *y1 = rect->y1;
  *x2 = rect->x2;
  *y2 = rect->y2;
}

void Annot::setRect(PDFRectangle *rect) {
    setRect(rect->x1, rect->y1, rect->x2, rect->y2);
}

void Annot::setRect(double x1, double y1, double x2, double y2) {
  Object obj1, obj2;

  if (x1 < x2) {
    rect->x1 = x1;
    rect->x2 = x2;
  } else {
    rect->x1 = x2;
    rect->x2 = x1;
  }

  if (y1 < y2) {
    rect->y1 = y1;
    rect->y2 = y2;
  } else {
    rect->y1 = y2;
    rect->y2 = y1;
  }

  obj1.initArray (xref);
  obj1.arrayAdd (obj2.initReal (rect->x1));
  obj1.arrayAdd (obj2.initReal (rect->y1));
  obj1.arrayAdd (obj2.initReal (rect->x2));
  obj1.arrayAdd (obj2.initReal (rect->y2));

  update("Rect", &obj1);
  invalidateAppearance();
}

GBool Annot::inRect(double x, double y) const {
  return rect->contains(x, y);
}

void Annot::update(const char *key, Object *value) {
  annotLocker();
  /* Set M to current time, unless we are updating M itself */
  if (strcmp(key, "M") != 0) {
    delete modified;
    modified = timeToDateString(NULL);

    Object obj1;
    obj1.initString (modified->copy());
    annotObj.dictSet("M", &obj1);
  }

  annotObj.dictSet(const_cast<char*>(key), value);
  
  xref->setModifiedObject(&annotObj, ref);
}

void Annot::setContents(GooString *new_content) {
  annotLocker();
  delete contents;

  if (new_content) {
    contents = new GooString(new_content);
    //append the unicode marker <FE FF> if needed	
    if (!contents->hasUnicodeMarker()) {
      contents->insert(0, 0xff);
      contents->insert(0, 0xfe);
    }
  } else {
    contents = new GooString();
  }
  
  Object obj1;
  obj1.initString(contents->copy());
  update ("Contents", &obj1);
}

void Annot::setName(GooString *new_name) {
  annotLocker();
  delete name;

  if (new_name) {
    name = new GooString(new_name);
  } else {
    name = new GooString();
  }

  Object obj1;
  obj1.initString(name->copy());
  update ("NM", &obj1);
}

void Annot::setModified(GooString *new_modified) {
  annotLocker();
  delete modified;

  if (new_modified)
    modified = new GooString(new_modified);
  else
    modified = new GooString();

  Object obj1;
  obj1.initString(modified->copy());
  update ("M", &obj1);
}

void Annot::setFlags(Guint new_flags) {
  annotLocker();
  Object obj1;
  flags = new_flags;
  obj1.initInt(flags);
  update ("F", &obj1);
}

void Annot::setBorder(AnnotBorder *new_border) {
  annotLocker();
  delete border;

  if (new_border) {
    Object obj1;
    new_border->writeToObject(xref, &obj1);
    update(new_border->getType() == AnnotBorder::typeArray ? "Border" : "BS", &obj1);
    border = new_border;
  } else {
    border = NULL;
  }
  invalidateAppearance();
}

void Annot::setColor(AnnotColor *new_color) {
  annotLocker();
  delete color;

  if (new_color) {
    Object obj1;
    new_color->writeToObject(xref, &obj1);
    update ("C", &obj1);
    color = new_color;
  } else {
    color = NULL;
  }
  invalidateAppearance();
}

void Annot::setPage(int pageIndex, GBool updateP) {
  annotLocker();
  Page *pageobj = doc->getPage(pageIndex);
  Object obj1;

  if (pageobj) {
    Ref pageRef = pageobj->getRef();
    obj1.initRef(pageRef.num, pageRef.gen);
    page = pageIndex;
  } else {
    obj1.initNull();
    page = 0;
  }

  if (updateP) {
    update("P", &obj1);
  }
}

void Annot::setAppearanceState(const char *state) {
  annotLocker();
  if (!state)
    return;

  delete appearState;
  appearState = new GooString(state);

  delete appearBBox;
  appearBBox = NULL;

  Object obj1;
  obj1.initName(state);
  update ("AS", &obj1);

  // The appearance state determines the current appearance stream
  appearance.free();
  if (appearStreams) {
    appearStreams->getAppearanceStream(AnnotAppearance::appearNormal, appearState->getCString(), &appearance);
  } else {
    appearance.initNull();
  }
}

void Annot::invalidateAppearance() {
  annotLocker();
  if (appearStreams) { // Remove existing appearance streams
    appearStreams->removeAllStreams();
  }
  delete appearStreams;
  appearStreams = NULL;

  delete appearState;
  appearState = NULL;

  delete appearBBox;
  appearBBox = NULL;

  appearance.free();
  appearance.initNull();

  Object obj1, obj2;
  obj1.initNull();
  if (!annotObj.dictLookup("AP", &obj2)->isNull())
    update ("AP", &obj1); // Remove AP
  obj2.free();

  if (!annotObj.dictLookup("AS", &obj2)->isNull())
    update ("AS", &obj1); // Remove AS
  obj2.free();
}

double Annot::getXMin() {
  return rect->x1;
}

double Annot::getYMin() {
  return rect->y1;
}

double Annot::getXMax() {
  return rect->x2;
}

double Annot::getYMax() {
  return rect->y2;
}

void Annot::readArrayNum(Object *pdfArray, int key, double *value) {
  Object valueObject;

  pdfArray->arrayGet(key, &valueObject);
  if (valueObject.isNum()) {
    *value = valueObject.getNum();
  } else {
    *value = 0;
    ok = gFalse;
  }
  valueObject.free();
}

void Annot::removeReferencedObjects() {
  // Remove appearance streams (if any)
  invalidateAppearance();
}

void Annot::incRefCnt() {
  annotLocker();
  refCnt++;
}

void Annot::decRefCnt() {
#if MULTITHREADED
  gLockMutex(&mutex);
#endif
  if (--refCnt == 0) {
#if MULTITHREADED
    gUnlockMutex(&mutex);
#endif
    delete this;
    return;
  }
#if MULTITHREADED
  gUnlockMutex(&mutex);
#endif
}

Annot::~Annot() {
  annotObj.free();
  
  delete rect;
  delete contents;

  if (name)
    delete name;

  if (modified)
    delete modified;

  delete appearStreams;
  delete appearBBox;
  appearance.free();

  if (appearState)
    delete appearState;

  if (border)
    delete border;

  if (color)
    delete color;

  oc.free();

#if MULTITHREADED
    gDestroyMutex(&mutex);
#endif
}

void Annot::setColor(AnnotColor *color, GBool fill) {
  const double *values = color->getValues();

  switch (color->getSpace()) {
  case AnnotColor::colorCMYK:
    appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:c}\n",
		       values[0], values[1], values[2], values[3],
		       fill ? 'k' : 'K');
    break;
  case AnnotColor::colorRGB:
    appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:s}\n",
		       values[0], values[1], values[2],
		       fill ? "rg" : "RG");
    break;
  case AnnotColor::colorGray:
    appearBuf->appendf("{0:.2f} {1:c}\n",
		       values[0],
		       fill ? 'g' : 'G');
    break;
  case AnnotColor::colorTransparent:
  default:
    break;
  }
}

void Annot::setLineStyleForBorder(AnnotBorder *border) {
  int i, dashLength;
  double *dash;

  switch (border->getStyle()) {
  case AnnotBorder::borderDashed:
    appearBuf->append("[");
    dashLength = border->getDashLength();
    dash = border->getDash();
    for (i = 0; i < dashLength; ++i)
      appearBuf->appendf(" {0:.2f}", dash[i]);
    appearBuf->append(" ] 0 d\n");
    break;
  default:
    appearBuf->append("[] 0 d\n");
    break;
  }
  appearBuf->appendf("{0:.2f} w\n", border->getWidth());
}

// Draw an (approximate) circle of radius <r> centered at (<cx>, <cy>).
// If <fill> is true, the circle is filled; otherwise it is stroked.
void Annot::drawCircle(double cx, double cy, double r, GBool fill) {
  appearBuf->appendf("{0:.2f} {1:.2f} m\n",
      cx + r, cy);
  appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
      cx + r, cy + bezierCircle * r,
      cx + bezierCircle * r, cy + r,
      cx, cy + r);
  appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
      cx - bezierCircle * r, cy + r,
      cx - r, cy + bezierCircle * r,
      cx - r, cy);
  appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
      cx - r, cy - bezierCircle * r,
      cx - bezierCircle * r, cy - r,
      cx, cy - r);
  appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
      cx + bezierCircle * r, cy - r,
      cx + r, cy - bezierCircle * r,
      cx + r, cy);
  appearBuf->append(fill ? "f\n" : "s\n");
}

// Draw the top-left half of an (approximate) circle of radius <r>
// centered at (<cx>, <cy>).
void Annot::drawCircleTopLeft(double cx, double cy, double r) {
  double r2;

  r2 = r / sqrt(2.0);
  appearBuf->appendf("{0:.2f} {1:.2f} m\n",
      cx + r2, cy + r2);
  appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
      cx + (1 - bezierCircle) * r2,
      cy + (1 + bezierCircle) * r2,
      cx - (1 - bezierCircle) * r2,
      cy + (1 + bezierCircle) * r2,
      cx - r2,
      cy + r2);
  appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
      cx - (1 + bezierCircle) * r2,
      cy + (1 - bezierCircle) * r2,
      cx - (1 + bezierCircle) * r2,
      cy - (1 - bezierCircle) * r2,
      cx - r2,
      cy - r2);
  appearBuf->append("S\n");
}

// Draw the bottom-right half of an (approximate) circle of radius <r>
// centered at (<cx>, <cy>).
void Annot::drawCircleBottomRight(double cx, double cy, double r) {
  double r2;

  r2 = r / sqrt(2.0);
  appearBuf->appendf("{0:.2f} {1:.2f} m\n",
      cx - r2, cy - r2);
  appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
      cx - (1 - bezierCircle) * r2,
      cy - (1 + bezierCircle) * r2,
      cx + (1 - bezierCircle) * r2,
      cy - (1 + bezierCircle) * r2,
      cx + r2,
      cy - r2);
  appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
      cx + (1 + bezierCircle) * r2,
      cy - (1 - bezierCircle) * r2,
      cx + (1 + bezierCircle) * r2,
      cy + (1 - bezierCircle) * r2,
      cx + r2,
      cy + r2);
  appearBuf->append("S\n");
}

void Annot::createForm(double *bbox, GBool transparencyGroup, Object *resDict, Object *aStream) {
  Object obj1, obj2;
  Object appearDict;

  appearDict.initDict(xref);
  appearDict.dictSet("Length", obj1.initInt(appearBuf->getLength()));
  appearDict.dictSet("Subtype", obj1.initName("Form"));
  obj1.initArray(xref);
  obj1.arrayAdd(obj2.initReal(bbox[0]));
  obj1.arrayAdd(obj2.initReal(bbox[1]));
  obj1.arrayAdd(obj2.initReal(bbox[2]));
  obj1.arrayAdd(obj2.initReal(bbox[3]));
  appearDict.dictSet("BBox", &obj1);
  if (transparencyGroup) {
    Object transDict;
    transDict.initDict(xref);
    transDict.dictSet("S", obj1.initName("Transparency"));
    appearDict.dictSet("Group", &transDict);
  }
  if (resDict)
    appearDict.dictSet("Resources", resDict);

  MemStream *mStream = new MemStream(copyString(appearBuf->getCString()), 0,
				     appearBuf->getLength(), &appearDict);
  mStream->setNeedFree(gTrue);
  aStream->initStream(mStream);
}

void Annot::createResourcesDict(const char *formName, Object *formStream,
				const char *stateName,
				double opacity, const char *blendMode,
				Object *resDict) {
  Object gsDict, stateDict, formDict, obj1;

  gsDict.initDict(xref);
  if (opacity != 1) {
    gsDict.dictSet("CA", obj1.initReal(opacity));
    gsDict.dictSet("ca", obj1.initReal(opacity));
  }
  if (blendMode)
    gsDict.dictSet("BM", obj1.initName(blendMode));
  stateDict.initDict(xref);
  stateDict.dictSet(stateName, &gsDict);
  formDict.initDict(xref);
  formDict.dictSet(formName, formStream);

  resDict->initDict(xref);
  resDict->dictSet("ExtGState", &stateDict);
  resDict->dictSet("XObject", &formDict);
}

Object *Annot::getAppearanceResDict(Object *dest) {
  Object obj1, obj2;

  dest->initNull(); // Default value

  // Fetch appearance's resource dict (if any)
  appearance.fetch(xref, &obj1);
  if (obj1.isStream()) {
    obj1.streamGetDict()->lookup("Resources", &obj2);
    if (obj2.isDict()) {
      obj2.copy(dest);
    }
    obj2.free();
  }
  obj1.free();

  return dest;
}

GBool Annot::isVisible(GBool printing) {
  // check the flags
  if ((flags & flagHidden) ||
      (printing && !(flags & flagPrint)) ||
      (!printing && (flags & flagNoView))) {
    return gFalse;
  }

  // check the OC
  OCGs *optContentConfig = doc->getCatalog()->getOptContentConfig();
  if (optContentConfig) {
    if (! optContentConfig->optContentIsVisible(&oc))
      return gFalse;
  }

  return gTrue;
}

int Annot::getRotation() const
{
  Page *pageobj = doc->getPage(page);
  assert(pageobj != NULL);

  if (flags & flagNoRotate) {
    return (360 - pageobj->getRotate()) % 360;
  } else {
    return 0;
  }
}

void Annot::draw(Gfx *gfx, GBool printing) {
  Object obj;

  annotLocker();
  if (!isVisible (printing))
    return;

  // draw the appearance stream
  appearance.fetch(gfx->getXRef(), &obj);
  gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color,
      rect->x1, rect->y1, rect->x2, rect->y2, getRotation());
  obj.free();
}

//------------------------------------------------------------------------
// AnnotPopup
//------------------------------------------------------------------------

AnnotPopup::AnnotPopup(PDFDoc *docA, PDFRectangle *rect) :
    Annot(docA, rect) {
  Object obj1;

  type = typePopup;

  annotObj.dictSet ("Subtype", obj1.initName ("Popup"));
  initialize (docA, annotObj.getDict());
}

AnnotPopup::AnnotPopup(PDFDoc *docA, Dict *dict, Object *obj) :
    Annot(docA, dict, obj) {
  type = typePopup;
  initialize(docA, dict);
}

AnnotPopup::~AnnotPopup() {
  parent.free();
}

void AnnotPopup::initialize(PDFDoc *docA, Dict *dict) {
  Object obj1;

  if (!dict->lookupNF("Parent", &parent)->isRef()) {
    parent.initNull();
  }

  if (dict->lookup("Open", &obj1)->isBool()) {
    open = obj1.getBool();
  } else {
    open = gFalse;
  }
  obj1.free();
}

void AnnotPopup::setParent(Object *parentA) {
  parentA->copy(&parent);
  update ("Parent", &parent);
}

void AnnotPopup::setParent(Annot *parentA) {
  Ref parentRef = parentA->getRef();
  parent.initRef(parentRef.num, parentRef.gen);
  update ("Parent", &parent);
}

void AnnotPopup::setOpen(GBool openA) {
  Object obj1;

  open = openA;
  obj1.initBool(open);
  update ("Open", &obj1);
}

//------------------------------------------------------------------------
// AnnotMarkup
//------------------------------------------------------------------------
AnnotMarkup::AnnotMarkup(PDFDoc *docA, PDFRectangle *rect) :
    Annot(docA, rect) {
  initialize(docA, annotObj.getDict(), &annotObj);
}

AnnotMarkup::AnnotMarkup(PDFDoc *docA, Dict *dict, Object *obj) :
    Annot(docA, dict, obj) {
  initialize(docA, dict, obj);
}

AnnotMarkup::~AnnotMarkup() {
  if (label)
    delete label;

  if (popup)
    delete popup;

  if (date)
    delete date;

  if (subject)
    delete subject;
}

void AnnotMarkup::initialize(PDFDoc *docA, Dict *dict, Object *obj) {
  Object obj1, obj2;

  if (dict->lookup("T", &obj1)->isString()) {
    label = obj1.getString()->copy();
  } else {
    label = NULL;
  }
  obj1.free();

  if (dict->lookup("Popup", &obj1)->isDict() && dict->lookupNF("Popup", &obj2)->isRef()) {
    popup = new AnnotPopup(docA, obj1.getDict(), &obj2);
  } else {
    popup = NULL;
  }
  obj1.free();

  if (dict->lookup("CA", &obj1)->isNum()) {
    opacity = obj1.getNum();
  } else {
    opacity = 1.0;
  }
  obj1.free();

  if (dict->lookup("CreationDate", &obj1)->isString()) {
    date = obj1.getString()->copy();
  } else {
    date = NULL;
  }
  obj1.free();

  if (dict->lookupNF("IRT", &obj1)->isRef()) {
    inReplyTo = obj1.getRef();
  } else {
    inReplyTo.num = 0;
    inReplyTo.gen = 0;
  }
  obj1.free();

  if (dict->lookup("Subj", &obj1)->isString()) {
    subject = obj1.getString()->copy();
  } else {
    subject = NULL;
  }
  obj1.free();

  if (dict->lookup("RT", &obj1)->isName()) {
    const char *replyName = obj1.getName();

    if (!strcmp(replyName, "R")) {
      replyTo = replyTypeR;
    } else if (!strcmp(replyName, "Group")) {
      replyTo = replyTypeGroup;
    } else {
      replyTo = replyTypeR;
    }
  } else {
    replyTo = replyTypeR;
  }
  obj1.free();

  if (dict->lookup("ExData", &obj1)->isDict()) {
    exData = parseAnnotExternalData(obj1.getDict());
  } else {
    exData = annotExternalDataMarkupUnknown;
  }
  obj1.free();
}

void AnnotMarkup::setLabel(GooString *new_label) {
  delete label;

  if (new_label) {
    label = new GooString(new_label);
    //append the unicode marker <FE FF> if needed
    if (!label->hasUnicodeMarker()) {
      label->insert(0, 0xff);
      label->insert(0, 0xfe);
    }
  } else {
    label = new GooString();
  }

  Object obj1;
  obj1.initString(label->copy());
  update ("T", &obj1);
}

void AnnotMarkup::setPopup(AnnotPopup *new_popup) {
  // If there exists an old popup annotation that is already
  // associated with a page, then we need to remove that
  // popup annotation from the page. Otherwise we would have
  // dangling references to it.
  if (popup != NULL && popup->getPageNum() != 0) {
    Page *pageobj = doc->getPage(popup->getPageNum());
    if (pageobj) {
      pageobj->removeAnnot(popup);
    }
  }
  delete popup;

  if (new_popup) {
    Object obj1;
    Ref popupRef = new_popup->getRef();

    obj1.initRef (popupRef.num, popupRef.gen);
    update ("Popup", &obj1);

    new_popup->setParent(this);
    popup = new_popup;

    // If this annotation is already added to a page, then we
    // add the new popup annotation to the same page.
    if (page != 0) {
      Page *pageobj = doc->getPage(page);
      assert(pageobj != NULL); // pageobj should exist in doc (see setPage())

      pageobj->addAnnot(popup);
    }
  } else {
    popup = NULL;
  }
}

void AnnotMarkup::setOpacity(double opacityA) {
  Object obj1;

  opacity = opacityA;
  obj1.initReal(opacity);
  update ("CA", &obj1);
  invalidateAppearance();
}

void AnnotMarkup::setDate(GooString *new_date) {
  delete date;

  if (new_date)
    date = new GooString(new_date);
  else
    date = new GooString();

  Object obj1;
  obj1.initString(date->copy());
  update ("CreationDate", &obj1);
}

void AnnotMarkup::removeReferencedObjects() {
  Page *pageobj = doc->getPage(page);
  assert(pageobj != NULL); // We're called when removing an annot from a page

  // Remove popup
  if (popup) {
    pageobj->removeAnnot(popup);
  }

  Annot::removeReferencedObjects();
}

//------------------------------------------------------------------------
// AnnotText
//------------------------------------------------------------------------

AnnotText::AnnotText(PDFDoc *docA, PDFRectangle *rect) :
    AnnotMarkup(docA, rect) {
  Object obj1;

  type = typeText;
  flags |= flagNoZoom | flagNoRotate;

  annotObj.dictSet ("Subtype", obj1.initName ("Text"));
  initialize (docA, annotObj.getDict());
}

AnnotText::AnnotText(PDFDoc *docA, Dict *dict, Object *obj) :
    AnnotMarkup(docA, dict, obj) {

  type = typeText;
  flags |= flagNoZoom | flagNoRotate;
  initialize (docA, dict);
}

AnnotText::~AnnotText() {
  delete icon;
}

void AnnotText::initialize(PDFDoc *docA, Dict *dict) {
  Object obj1;

  if (dict->lookup("Open", &obj1)->isBool())
    open = obj1.getBool();
  else
    open = gFalse;
  obj1.free();

  if (dict->lookup("Name", &obj1)->isName()) {
    icon = new GooString(obj1.getName());
  } else {
    icon = new GooString("Note");
  }
  obj1.free();

  if (dict->lookup("StateModel", &obj1)->isString()) {
    Object obj2;
    GooString *modelName = obj1.getString();

    if (dict->lookup("State", &obj2)->isString()) {
      GooString *stateName = obj2.getString();

      if (!stateName->cmp("Marked")) {
        state = stateMarked;
      } else if (!stateName->cmp("Unmarked")) {
        state = stateUnmarked;
      } else if (!stateName->cmp("Accepted")) {
        state = stateAccepted;
      } else if (!stateName->cmp("Rejected")) {
        state = stateRejected;
      } else if (!stateName->cmp("Cancelled")) {
        state = stateCancelled;
      } else if (!stateName->cmp("Completed")) {
        state = stateCompleted;
      } else if (!stateName->cmp("None")) {
        state = stateNone;
      } else {
        state = stateUnknown;
      }
    } else {
      state = stateUnknown;
    }
    obj2.free();

    if (!modelName->cmp("Marked")) {
      switch (state) {
        case stateUnknown:
          state = stateMarked;
          break;
        case stateAccepted:
        case stateRejected:
        case stateCancelled:
        case stateCompleted:
        case stateNone:
          state = stateUnknown;
          break;
        default:
          break;
      }
    } else if (!modelName->cmp("Review")) {
      switch (state) {
        case stateUnknown:
          state = stateNone;
          break;
        case stateMarked:
        case stateUnmarked:
          state = stateUnknown;
          break;
        default:
          break;
      }
    } else {
      state = stateUnknown;
    }
  } else {
    state = stateUnknown;
  }
  obj1.free();
}

void AnnotText::setOpen(GBool openA) {
  Object obj1;

  open = openA;
  obj1.initBool(open);
  update ("Open", &obj1);
}

void AnnotText::setIcon(GooString *new_icon) {
  if (new_icon && icon->cmp(new_icon) == 0)
    return;

  delete icon;

  if (new_icon) {
    icon = new GooString (new_icon);
  } else {
    icon = new GooString("Note");
  }

  Object obj1;
  obj1.initName (icon->getCString());
  update("Name", &obj1);
  invalidateAppearance();
}

#define ANNOT_TEXT_AP_NOTE                                                    \
  "3.602 24 m 20.398 24 l 22.387 24 24 22.387 24 20.398 c 24 3.602 l 24\n"    \
  "1.613 22.387 0 20.398 0 c 3.602 0 l 1.613 0 0 1.613 0 3.602 c 0 20.398\n"  \
  "l 0 22.387 1.613 24 3.602 24 c h\n"                                        \
  "3.602 24 m f\n"                                                            \
  "0.533333 0.541176 0.521569 RG 2 w\n"                                       \
  "1 J\n"                                                                     \
  "1 j\n"                                                                     \
  "[] 0.0 d\n"                                                                \
  "4 M 9 18 m 4 18 l 4 7 4 4 6 3 c 20 3 l 18 4 18 7 18 18 c 17 18 l S\n"      \
  "1.5 w\n"                                                                   \
  "0 j\n"                                                                     \
  "10 16 m 14 21 l S\n"                                                       \
  "1.85625 w\n"                                                               \
  "1 j\n"                                                                     \
  "15.07 20.523 m 15.07 19.672 14.379 18.977 13.523 18.977 c 12.672 18.977\n" \
  "11.977 19.672 11.977 20.523 c 11.977 21.379 12.672 22.07 13.523 22.07 c\n" \
  "14.379 22.07 15.07 21.379 15.07 20.523 c h\n"                              \
  "15.07 20.523 m S\n"                                                        \
  "1 w\n"                                                                     \
  "0 j\n"                                                                     \
  "6.5 13.5 m 15.5 13.5 l S\n"                                                \
  "6.5 10.5 m 13.5 10.5 l S\n"                                                \
  "6.801 7.5 m 15.5 7.5 l S\n"                                                \
  "0.729412 0.741176 0.713725 RG 2 w\n"                                       \
  "1 j\n"                                                                     \
  "9 19 m 4 19 l 4 8 4 5 6 4 c 20 4 l 18 5 18 8 18 19 c 17 19 l S\n"          \
  "1.5 w\n"                                                                   \
  "0 j\n"                                                                     \
  "10 17 m 14 22 l S\n"                                                       \
  "1.85625 w\n"                                                               \
  "1 j\n"                                                                     \
  "15.07 21.523 m 15.07 20.672 14.379 19.977 13.523 19.977 c 12.672 19.977\n" \
  "11.977 20.672 11.977 21.523 c 11.977 22.379 12.672 23.07 13.523 23.07 c\n" \
  "14.379 23.07 15.07 22.379 15.07 21.523 c h\n"                              \
  "15.07 21.523 m S\n"                                                        \
  "1 w\n"                                                                     \
  "0 j\n"                                                                     \
  "6.5 14.5 m 15.5 14.5 l S\n"                                                \
  "6.5 11.5 m 13.5 11.5 l S\n"                                                \
  "6.801 8.5 m 15.5 8.5 l S\n"

#define ANNOT_TEXT_AP_COMMENT                                                   \
  "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n"      \
  "2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n"    \
  "l 1 21.523 2.477 23 4.301 23 c h\n"                                          \
  "4.301 23 m f\n"                                                              \
  "0.533333 0.541176 0.521569 RG 2 w\n"                                         \
  "0 J\n"                                                                       \
  "1 j\n"                                                                       \
  "[] 0.0 d\n"                                                                  \
  "4 M 8 20 m 16 20 l 18.363 20 20 18.215 20 16 c 20 13 l 20 10.785 18.363 9\n" \
  "16 9 c 13 9 l 8 3 l 8 9 l 8 9 l 5.637 9 4 10.785 4 13 c 4 16 l 4 18.215\n"   \
  "5.637 20 8 20 c h\n"                                                         \
  "8 20 m S\n"                                                                  \
  "0.729412 0.741176 0.713725 RG 8 21 m 16 21 l 18.363 21 20 19.215 20 17\n"    \
  "c 20 14 l 20 11.785 18.363 10\n"                                             \
  "16 10 c 13 10 l 8 4 l 8 10 l 8 10 l 5.637 10 4 11.785 4 14 c 4 17 l 4\n"     \
  "19.215 5.637 21 8 21 c h\n"                                                  \
  "8 21 m S\n"

#define ANNOT_TEXT_AP_KEY                                                    \
  "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n"   \
  "2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n" \
  "l 1 21.523 2.477 23 4.301 23 c h\n"                                       \
  "4.301 23 m f\n"                                                           \
  "0.533333 0.541176 0.521569 RG 2 w\n"                                      \
  "1 J\n"                                                                    \
  "0 j\n"                                                                    \
  "[] 0.0 d\n"                                                               \
  "4 M 11.895 18.754 m 13.926 20.625 17.09 20.496 18.961 18.465 c 20.832\n"  \
  "16.434 20.699 13.27 18.668 11.398 c 17.164 10.016 15.043 9.746 13.281\n"  \
  "10.516 c 12.473 9.324 l 11.281 10.078 l 9.547 8.664 l 9.008 6.496 l\n"    \
  "7.059 6.059 l 6.34 4.121 l 5.543 3.668 l 3.375 4.207 l 2.938 6.156 l\n"   \
  "10.57 13.457 l 9.949 15.277 10.391 17.367 11.895 18.754 c h\n"            \
  "11.895 18.754 m S\n"                                                      \
  "1.5 w\n"                                                                  \
  "16.059 15.586 m 16.523 15.078 17.316 15.043 17.824 15.512 c 18.332\n"     \
  "15.98 18.363 16.77 17.895 17.277 c 17.43 17.785 16.637 17.816 16.129\n"   \
  "17.352 c 15.621 16.883 15.59 16.094 16.059 15.586 c h\n"                  \
  "16.059 15.586 m S\n"                                                      \
  "0.729412 0.741176 0.713725 RG 2 w\n"                                      \
  "11.895 19.754 m 13.926 21.625 17.09 21.496 18.961 19.465 c 20.832\n"      \
  "17.434 20.699 14.27 18.668 12.398 c 17.164 11.016 15.043 10.746 13.281\n" \
  "11.516 c 12.473 10.324 l 11.281 11.078 l 9.547 9.664 l 9.008 7.496 l\n"   \
  "7.059 7.059 l 6.34 5.121 l 5.543 4.668 l 3.375 5.207 l 2.938 7.156 l\n"   \
  "10.57 14.457 l 9.949 16.277 10.391 18.367 11.895 19.754 c h\n"            \
  "11.895 19.754 m S\n"                                                      \
  "1.5 w\n"                                                                  \
  "16.059 16.586 m 16.523 16.078 17.316 16.043 17.824 16.512 c 18.332\n"     \
  "16.98 18.363 17.77 17.895 18.277 c 17.43 18.785 16.637 18.816 16.129\n"   \
  "18.352 c 15.621 17.883 15.59 17.094 16.059 16.586 c h\n"                  \
  "16.059 16.586 m S\n"

#define ANNOT_TEXT_AP_HELP                                                        \
  "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n"        \
  "2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n"      \
  "l 1 21.523 2.477 23 4.301 23 c h\n"                                            \
  "4.301 23 m f\n"                                                                \
  "0.533333 0.541176 0.521569 RG 2.5 w\n"                                         \
  "1 J\n"                                                                         \
  "1 j\n"                                                                         \
  "[] 0.0 d\n"                                                                    \
  "4 M 8.289 16.488 m 8.824 17.828 10.043 18.773 11.473 18.965 c 12.902 19.156\n" \
  "14.328 18.559 15.195 17.406 c 16.062 16.254 16.242 14.723 15.664 13.398\n"     \
  "c S\n"                                                                         \
  "0 j\n"                                                                         \
  "12 8 m 12 12 16 11 16 15 c S\n"                                                \
  "1.539286 w\n"                                                                  \
  "1 j\n"                                                                         \
  "q 1 0 0 -0.999991 0 24 cm\n"                                                   \
  "12.684 20.891 m 12.473 21.258 12.004 21.395 11.629 21.196 c 11.254\n"          \
  "20.992 11.105 20.531 11.297 20.149 c 11.488 19.77 11.945 19.61 12.332\n"       \
  "19.789 c 12.719 19.969 12.891 20.426 12.719 20.817 c S Q\n"                    \
  "0.729412 0.741176 0.713725 RG 2.5 w\n"                                         \
  "8.289 17.488 m 9.109 19.539 11.438 20.535 13.488 19.711 c 15.539 18.891\n"     \
  "16.535 16.562 15.711 14.512 c 15.699 14.473 15.684 14.438 15.664 14.398\n"     \
  "c S\n"                                                                         \
  "0 j\n"                                                                         \
  "12 9 m 12 13 16 12 16 16 c S\n"                                                \
  "1.539286 w\n"                                                                  \
  "1 j\n"                                                                         \
  "q 1 0 0 -0.999991 0 24 cm\n"                                                   \
  "12.684 19.891 m 12.473 20.258 12.004 20.395 11.629 20.195 c 11.254\n"          \
  "19.992 11.105 19.531 11.297 19.149 c 11.488 18.77 11.945 18.61 12.332\n"       \
  "18.789 c 12.719 18.969 12.891 19.426 12.719 19.817 c S Q\n"

#define ANNOT_TEXT_AP_NEW_PARAGRAPH                                               \
  "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n"        \
  "2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n"      \
  "l 1 21.523 2.477 23 4.301 23 c h\n"                                            \
  "4.301 23 m f\n"                                                                \
  "0.533333 0.541176 0.521569 RG 4 w\n"                                           \
  "0 J\n"                                                                         \
  "2 j\n"                                                                         \
  "[] 0.0 d\n"                                                                    \
  "4 M q 1 0 0 -1 0 24 cm\n"                                                      \
  "9.211 11.988 m 8.449 12.07 7.711 11.707 7.305 11.059 c 6.898 10.41\n"          \
  "6.898 9.59 7.305 8.941 c 7.711 8.293 8.449 7.93 9.211 8.012 c S Q\n"           \
  "1.004413 w\n"                                                                  \
  "1 J\n"                                                                         \
  "1 j\n"                                                                         \
  "q 1 0 0 -0.991232 0 24 cm\n"                                                   \
  "18.07 11.511 m 15.113 10.014 l 12.199 11.602 l 12.711 8.323 l 10.301\n"        \
  "6.045 l 13.574 5.517 l 14.996 2.522 l 16.512 5.474 l 19.801 5.899 l\n"         \
  "17.461 8.252 l 18.07 11.511 l h\n"                                             \
  "18.07 11.511 m S Q\n"                                                          \
  "2 w\n"                                                                         \
  "0 j\n"                                                                         \
  "11 17 m 10 17 l 10 3 l S\n"                                                    \
  "14 3 m 14 13 l S\n"                                                            \
  "0.729412 0.741176 0.713725 RG 4 w\n"                                           \
  "0 J\n"                                                                         \
  "2 j\n"                                                                         \
  "q 1 0 0 -1 0 24 cm\n"                                                          \
  "9.211 10.988 m 8.109 11.105 7.125 10.309 7.012 9.211 c 6.895 8.109\n"          \
  "7.691 7.125 8.789 7.012 c 8.93 6.996 9.07 6.996 9.211 7.012 c S Q\n"           \
  "1.004413 w\n"                                                                  \
  "1 J\n"                                                                         \
  "1 j\n"                                                                         \
  "q 1 0 0 -0.991232 0 24 cm\n"                                                   \
  "18.07 10.502 m 15.113 9.005 l 12.199 10.593 l 12.711 7.314 l 10.301\n"         \
  "5.036 l 13.574 4.508 l 14.996 1.513 l 16.512 4.465 l 19.801 4.891 l\n"         \
  "17.461 7.243 l 18.07 10.502 l h\n"                                             \
  "18.07 10.502 m S Q\n"                                                          \
  "2 w\n"                                                                         \
  "0 j\n"                                                                         \
  "11 18 m 10 18 l 10 4 l S\n"                                                    \
  "14 4 m 14 14 l S\n"

#define ANNOT_TEXT_AP_PARAGRAPH                                              \
  "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n"   \
  "2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n" \
  "l 1 21.523 2.477 23 4.301 23 c h\n"                                       \
  "4.301 23 m f\n"                                                           \
  "0.533333 0.541176 0.521569 RG 2 w\n"                                      \
  "1 J\n"                                                                    \
  "1 j\n"                                                                    \
  "[] 0.0 d\n"                                                               \
  "4 M 15 3 m 15 18 l 11 18 l 11 3 l S\n"                                    \
  "4 w\n"                                                                    \
  "q 1 0 0 -1 0 24 cm\n"                                                     \
  "9.777 10.988 m 8.746 10.871 7.973 9.988 8 8.949 c 8.027 7.91 8.844\n"     \
  "7.066 9.879 7.004 c S Q\n"                                                \
  "0.729412 0.741176 0.713725 RG 2 w\n"                                      \
  "15 4 m 15 19 l 11 19 l 11 4 l S\n"                                        \
  "4 w\n"                                                                    \
  "q 1 0 0 -1 0 24 cm\n"                                                     \
  "9.777 9.988 m 8.746 9.871 7.973 8.988 8 7.949 c 8.027 6.91 8.844 6.066\n" \
  "9.879 6.004 c S Q\n"

#define ANNOT_TEXT_AP_INSERT                                                 \
  "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n"   \
  "2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n" \
  "l 1 21.523 2.477 23 4.301 23 c h\n"                                       \
  "4.301 23 m f\n"                                                           \
  "0.533333 0.541176 0.521569 RG 2 w\n"                                      \
  "1 J\n"                                                                    \
  "0 j\n"                                                                    \
  "[] 0.0 d\n"                                                               \
  "4 M 12 18.012 m 20 18 l S\n"                                              \
  "9 10 m 17 10 l S\n"                                                       \
  "12 14.012 m 20 14 l S\n"                                                  \
  "12 6.012 m 20 6.012 l S\n"                                                \
  "4 12 m 6 10 l 4 8 l S\n"                                                  \
  "4 12 m 4 8 l S\n"                                                         \
  "0.729412 0.741176 0.713725 RG 12 19.012 m 20 19 l S\n"                    \
  "9 11 m 17 11 l S\n"                                                       \
  "12 15.012 m 20 15 l S\n"                                                  \
  "12 7.012 m 20 7.012 l S\n"                                                \
  "4 13 m 6 11 l 4 9 l S\n"                                                  \
  "4 13 m 4 9 l S\n"

#define ANNOT_TEXT_AP_CROSS                                                  \
  "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n"   \
  "2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n" \
  "l 1 21.523 2.477 23 4.301 23 c h\n"                                       \
  "4.301 23 m f\n"                                                           \
  "0.533333 0.541176 0.521569 RG 2.5 w\n"                                    \
  "1 J\n"                                                                    \
  "0 j\n"                                                                    \
  "[] 0.0 d\n"                                                               \
  "4 M 18 5 m 6 17 l S\n"                                                    \
  "6 5 m 18 17 l S\n"                                                        \
  "0.729412 0.741176 0.713725 RG 18 6 m 6 18 l S\n"                          \
  "6 6 m 18 18 l S\n"

#define ANNOT_TEXT_AP_CIRCLE                                                      \
  "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n"        \
  "2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n"      \
  "l 1 21.523 2.477 23 4.301 23 c h\n"                                            \
  "4.301 23 m f\n"                                                                \
  "0.533333 0.541176 0.521569 RG 2.5 w\n"                                         \
  "1 J\n"                                                                         \
  "1 j\n"                                                                         \
  "[] 0.0 d\n"                                                                    \
  "4 M 19.5 11.5 m 19.5 7.359 16.141 4 12 4 c 7.859 4 4.5 7.359 4.5 11.5 c 4.5\n" \
  "15.641 7.859 19 12 19 c 16.141 19 19.5 15.641 19.5 11.5 c h\n"                 \
  "19.5 11.5 m S\n"                                                               \
  "0.729412 0.741176 0.713725 RG 19.5 12.5 m 19.5 8.359 16.141 5 12 5 c\n"        \
  "7.859 5 4.5 8.359 4.5 12.5 c 4.5\n"                                            \
  "16.641 7.859 20 12 20 c 16.141 20 19.5 16.641 19.5 12.5 c h\n"                 \
  "19.5 12.5 m S\n"

void AnnotText::draw(Gfx *gfx, GBool printing) {
  Object obj;
  double ca = 1;

  if (!isVisible (printing))
    return;

  annotLocker();
  if (appearance.isNull()) {
    ca = opacity;

    appearBuf = new GooString ();

    appearBuf->append ("q\n");
    if (color)
      setColor(color, gTrue);
    else
      appearBuf->append ("1 1 1 rg\n");
    if (!icon->cmp("Note"))
      appearBuf->append (ANNOT_TEXT_AP_NOTE);
    else if (!icon->cmp("Comment"))
      appearBuf->append (ANNOT_TEXT_AP_COMMENT);
    else if (!icon->cmp("Key"))
      appearBuf->append (ANNOT_TEXT_AP_KEY);
    else if (!icon->cmp("Help"))
      appearBuf->append (ANNOT_TEXT_AP_HELP);
    else if (!icon->cmp("NewParagraph"))
      appearBuf->append (ANNOT_TEXT_AP_NEW_PARAGRAPH);
    else if (!icon->cmp("Paragraph"))
      appearBuf->append (ANNOT_TEXT_AP_PARAGRAPH);
    else if (!icon->cmp("Insert"))
      appearBuf->append (ANNOT_TEXT_AP_INSERT);
    else if (!icon->cmp("Cross"))
      appearBuf->append (ANNOT_TEXT_AP_CROSS);
    else if (!icon->cmp("Circle"))
      appearBuf->append (ANNOT_TEXT_AP_CIRCLE);
    appearBuf->append ("Q\n");

    // Force 24x24 rectangle
    PDFRectangle fixedRect(rect->x1, rect->y2 - 24, rect->x1 + 24, rect->y2);
    appearBBox = new AnnotAppearanceBBox(&fixedRect);
    double bbox[4];
    appearBBox->getBBoxRect(bbox);
    if (ca == 1) {
      createForm(bbox, gFalse, NULL, &appearance);
    } else {
      Object aStream, resDict;

      createForm(bbox, gTrue, NULL, &aStream);
      delete appearBuf;

      appearBuf = new GooString ("/GS0 gs\n/Fm0 Do");
      createResourcesDict("Fm0", &aStream, "GS0", ca, NULL, &resDict);
      createForm(bbox, gFalse, &resDict, &appearance);
    }
    delete appearBuf;
  }

  // draw the appearance stream
  appearance.fetch(gfx->getXRef(), &obj);
  if (appearBBox) {
    gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color,
                   appearBBox->getPageXMin(), appearBBox->getPageYMin(),
                   appearBBox->getPageXMax(), appearBBox->getPageYMax(),
                   getRotation());
  } else {
    gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color,
                   rect->x1, rect->y1, rect->x2, rect->y2, getRotation());
  }
  obj.free();
}

//------------------------------------------------------------------------
// AnnotLink
//------------------------------------------------------------------------
AnnotLink::AnnotLink(PDFDoc *docA, PDFRectangle *rect) :
    Annot(docA, rect) {
  Object obj1;

  type = typeLink;
  annotObj.dictSet ("Subtype", obj1.initName ("Link"));
  initialize (docA, annotObj.getDict());
}

AnnotLink::AnnotLink(PDFDoc *docA, Dict *dict, Object *obj) :
    Annot(docA, dict, obj) {

  type = typeLink;
  initialize (docA, dict);
}

AnnotLink::~AnnotLink() {
  delete action;
  /*
  if (uriAction)
    delete uriAction;
  */
  if (quadrilaterals)
    delete quadrilaterals;
}

void AnnotLink::initialize(PDFDoc *docA, Dict *dict) {
  Object obj1;

  action = NULL;

  // look for destination
  if (!dict->lookup("Dest", &obj1)->isNull()) {
    action = LinkAction::parseDest(&obj1);
  // look for action
  } else {
    obj1.free();
    if (dict->lookup("A", &obj1)->isDict()) {
      action = LinkAction::parseAction(&obj1, doc->getCatalog()->getBaseURI());
    }
  }
  obj1.free();

  if (dict->lookup("H", &obj1)->isName()) {
    const char *effect = obj1.getName();

    if (!strcmp(effect, "N")) {
      linkEffect = effectNone;
    } else if (!strcmp(effect, "I")) {
      linkEffect = effectInvert;
    } else if (!strcmp(effect, "O")) {
      linkEffect = effectOutline;
    } else if (!strcmp(effect, "P")) {
      linkEffect = effectPush;
    } else {
      linkEffect = effectInvert;
    }
  } else {
    linkEffect = effectInvert;
  }
  obj1.free();
  /*
  if (dict->lookup("PA", &obj1)->isDict()) {
    uriAction = NULL;
  } else {
    uriAction = NULL;
  }
  obj1.free();
  */
  if (dict->lookup("QuadPoints", &obj1)->isArray()) {
    quadrilaterals = new AnnotQuadrilaterals(obj1.getArray(), rect);
  } else {
    quadrilaterals = NULL;
  }
  obj1.free();

  if (dict->lookup("BS", &obj1)->isDict()) {
    delete border;
    border = new AnnotBorderBS(obj1.getDict());
  } else if (!border) {
    border = new AnnotBorderBS();
  }
  obj1.free();
}

void AnnotLink::draw(Gfx *gfx, GBool printing) {
  Object obj;

  if (!isVisible (printing))
    return;

  annotLocker();
  // draw the appearance stream
  appearance.fetch(gfx->getXRef(), &obj);
  gfx->drawAnnot(&obj, border, color,
		 rect->x1, rect->y1, rect->x2, rect->y2, getRotation());
  obj.free();
}

//------------------------------------------------------------------------
// AnnotFreeText
//------------------------------------------------------------------------
AnnotFreeText::AnnotFreeText(PDFDoc *docA, PDFRectangle *rect, GooString *da) :
    AnnotMarkup(docA, rect) {
  Object obj1;

  type = typeFreeText;

  annotObj.dictSet ("Subtype", obj1.initName ("FreeText"));

  Object obj2;
  obj2.initString (da->copy());
  annotObj.dictSet("DA", &obj2);

  initialize (docA, annotObj.getDict());
}

AnnotFreeText::AnnotFreeText(PDFDoc *docA, Dict *dict, Object *obj) :
    AnnotMarkup(docA, dict, obj) {
  type = typeFreeText;
  initialize(docA, dict);
}

AnnotFreeText::~AnnotFreeText() {
  delete appearanceString;

  if (styleString)
    delete styleString;

  if (calloutLine)
    delete calloutLine;

  if (borderEffect)
    delete borderEffect;

  if (rectangle)
    delete rectangle;
}

void AnnotFreeText::initialize(PDFDoc *docA, Dict *dict) {
  Object obj1;

  if (dict->lookup("DA", &obj1)->isString()) {
    appearanceString = obj1.getString()->copy();
  } else {
    appearanceString = new GooString();
    error(errSyntaxError, -1, "Bad appearance for annotation");
    ok = gFalse;
  }
  obj1.free();

  if (dict->lookup("Q", &obj1)->isInt()) {
    quadding = (AnnotFreeTextQuadding) obj1.getInt();
  } else {
    quadding = quaddingLeftJustified;
  }
  obj1.free();

  if (dict->lookup("DS", &obj1)->isString()) {
    styleString = obj1.getString()->copy();
  } else {
    styleString = NULL;
  }
  obj1.free();

  if (dict->lookup("CL", &obj1)->isArray() && obj1.arrayGetLength() >= 4) {
    double x1, y1, x2, y2;
    Object obj2;

    (obj1.arrayGet(0, &obj2)->isNum() ? x1 = obj2.getNum() : x1 = 0);
    obj2.free();
    (obj1.arrayGet(1, &obj2)->isNum() ? y1 = obj2.getNum() : y1 = 0);
    obj2.free();
    (obj1.arrayGet(2, &obj2)->isNum() ? x2 = obj2.getNum() : x2 = 0);
    obj2.free();
    (obj1.arrayGet(3, &obj2)->isNum() ? y2 = obj2.getNum() : y2 = 0);
    obj2.free();

    if (obj1.arrayGetLength() == 6) {
      double x3, y3;
      (obj1.arrayGet(4, &obj2)->isNum() ? x3 = obj2.getNum() : x3 = 0);
      obj2.free();
      (obj1.arrayGet(5, &obj2)->isNum() ? y3 = obj2.getNum() : y3 = 0);
      obj2.free();
      calloutLine = new AnnotCalloutMultiLine(x1, y1, x2, y2, x3, y3);
    } else {
      calloutLine = new AnnotCalloutLine(x1, y1, x2, y2);
    }
  } else {
    calloutLine = NULL;
  }
  obj1.free();

  if (dict->lookup("IT", &obj1)->isName()) {
    const char *intentName = obj1.getName();

    if (!strcmp(intentName, "FreeText")) {
      intent = intentFreeText;
    } else if (!strcmp(intentName, "FreeTextCallout")) {
      intent = intentFreeTextCallout;
    } else if (!strcmp(intentName, "FreeTextTypeWriter")) {
      intent = intentFreeTextTypeWriter;
    } else {
      intent = intentFreeText;
    }
  } else {
    intent = intentFreeText;
  }
  obj1.free();

  if (dict->lookup("BS", &obj1)->isDict()) {
    delete border;
    border = new AnnotBorderBS(obj1.getDict());
  } else if (!border) {
    border = new AnnotBorderBS();
  }
  obj1.free();

  if (dict->lookup("BE", &obj1)->isDict()) {
    borderEffect = new AnnotBorderEffect(obj1.getDict());
  } else {
    borderEffect = NULL;
  }
  obj1.free();

  if (dict->lookup("RD", &obj1)->isArray()) {
    rectangle = parseDiffRectangle(obj1.getArray(), rect);
  } else {
    rectangle = NULL;
  }
  obj1.free();

  if (dict->lookup("LE", &obj1)->isName()) {
    GooString styleName(obj1.getName());
    endStyle = parseAnnotLineEndingStyle(&styleName);
  } else {
    endStyle = annotLineEndingNone;
  }
  obj1.free();
}

void AnnotFreeText::setContents(GooString *new_content) {
  Annot::setContents(new_content);
  invalidateAppearance();
}

void AnnotFreeText::setAppearanceString(GooString *new_string) {
  delete appearanceString;

  if (new_string) {
    appearanceString = new GooString(new_string);
  } else {
    appearanceString = new GooString();
  }

  Object obj1;
  obj1.initString(appearanceString->copy());
  update ("DA", &obj1);
  invalidateAppearance();
}

void AnnotFreeText::setQuadding(AnnotFreeTextQuadding new_quadding) {
  Object obj1;
  quadding = new_quadding;
  obj1.initInt((int)quadding);
  update ("Q", &obj1);
  invalidateAppearance();
}

void AnnotFreeText::setStyleString(GooString *new_string) {
  delete styleString;

  if (new_string) {
    styleString = new GooString(new_string);
    //append the unicode marker <FE FF> if needed
    if (!styleString->hasUnicodeMarker()) {
      styleString->insert(0, 0xff);
      styleString->insert(0, 0xfe);
    }
  } else {
    styleString = new GooString();
  }

  Object obj1;
  obj1.initString(styleString->copy());
  update ("DS", &obj1);
}

void AnnotFreeText::setCalloutLine(AnnotCalloutLine *line) {
  delete calloutLine;

  Object obj1;
  if (line == NULL) {
    obj1.initNull();
    calloutLine = NULL;
  } else {
    double x1 = line->getX1(), y1 = line->getY1();
    double x2 = line->getX2(), y2 = line->getY2();
    Object obj2;
    obj1.initArray(xref);
    obj1.arrayAdd( obj2.initReal(x1) );
    obj1.arrayAdd( obj2.initReal(y1) );
    obj1.arrayAdd( obj2.initReal(x2) );
    obj1.arrayAdd( obj2.initReal(y2) );

    AnnotCalloutMultiLine *mline = dynamic_cast<AnnotCalloutMultiLine*>(line);
    if (mline) {
      double x3 = mline->getX3(), y3 = mline->getY3();
      obj1.arrayAdd( obj2.initReal(x3) );
      obj1.arrayAdd( obj2.initReal(y3) );
      calloutLine = new AnnotCalloutMultiLine(x1, y1, x2, y2, x3, y3);
    } else {
      calloutLine = new AnnotCalloutLine(x1, y1, x2, y2);
    }
  }

  update("CL", &obj1);
  invalidateAppearance();
}

void AnnotFreeText::setIntent(AnnotFreeTextIntent new_intent) {
  Object obj1;

  intent = new_intent;
  if (new_intent == intentFreeText)
    obj1.initName("FreeText");
  else if (new_intent == intentFreeTextCallout)
    obj1.initName("FreeTextCallout");
  else // intentFreeTextTypeWriter
    obj1.initName("FreeTextTypeWriter");
  update ("IT", &obj1);
}

static GfxFont * createAnnotDrawFont(XRef * xref, Object *fontResDict)
{
  Ref dummyRef = { -1, -1 };

  Object baseFontObj, subtypeObj, encodingObj;
  baseFontObj.initName("Helvetica");
  subtypeObj.initName("Type0");
  encodingObj.initName("WinAnsiEncoding");

  Object fontDictObj;
  Dict *fontDict = new Dict(xref);
  fontDict->decRef();
  fontDict->add(copyString("BaseFont"), &baseFontObj);
  fontDict->add(copyString("Subtype"), &subtypeObj);
  fontDict->add(copyString("Encoding"), &encodingObj);
  fontDictObj.initDict(fontDict);

  Object fontsDictObj;
  Dict *fontsDict = new Dict(xref);
  fontsDict->decRef();
  fontsDict->add(copyString("AnnotDrawFont"), &fontDictObj);
  fontsDictObj.initDict(fontsDict);

  Dict *dict = new Dict(xref);
  dict->add(copyString("Font"), &fontsDictObj);

  fontResDict->initDict(dict);
  return GfxFont::makeFont(xref, "AnnotDrawFont", dummyRef, fontDict);
}

void AnnotFreeText::parseAppearanceString(GooString *da, double &fontsize, AnnotColor* &fontcolor) {
  fontsize = -1;
  fontcolor = NULL;
  if (da) {
    GooList * daToks = new GooList();
    int j, i = 0;

    // Tokenize
    while (i < da->getLength()) {
      while (i < da->getLength() && Lexer::isSpace(da->getChar(i))) {
        ++i;
      }
      if (i < da->getLength()) {
        for (j = i + 1; j < da->getLength() && !Lexer::isSpace(da->getChar(j)); ++j) {
        }
        daToks->append(new GooString(da, i, j - i));
        i = j;
      }
    }

    // Scan backwards: we are looking for the last set value
    for (i = daToks->getLength()-1; i >= 0; --i) {
      if (fontsize == -1) {
        if (!((GooString *)daToks->get(i))->cmp("Tf") && i >= 2) {
            // TODO: Font name
            fontsize = gatof(( (GooString *)daToks->get(i-1) )->getCString());
        }
      }
      if (fontcolor == NULL) {
        if (!((GooString *)daToks->get(i))->cmp("g") && i >= 1) {
          fontcolor = new AnnotColor(gatof(( (GooString *)daToks->get(i-1) )->getCString()));
        } else if (!((GooString *)daToks->get(i))->cmp("rg") && i >= 3) {
          fontcolor = new AnnotColor(gatof(( (GooString *)daToks->get(i-3) )->getCString()),
                                     gatof(( (GooString *)daToks->get(i-2) )->getCString()),
                                     gatof(( (GooString *)daToks->get(i-1) )->getCString()));
        } else if (!((GooString *)daToks->get(i))->cmp("k") && i >= 4) {
          fontcolor = new AnnotColor(gatof(( (GooString *)daToks->get(i-4) )->getCString()),
                                     gatof(( (GooString *)daToks->get(i-3) )->getCString()),
                                     gatof(( (GooString *)daToks->get(i-2) )->getCString()),
                                     gatof(( (GooString *)daToks->get(i-1) )->getCString()));
        }
      }
    }
    deleteGooList(daToks, GooString);
  }
}

void AnnotFreeText::generateFreeTextAppearance()
{
  double borderWidth, ca = opacity;

  appearBuf = new GooString ();
  appearBuf->append ("q\n");

  borderWidth = border->getWidth();
  if (borderWidth > 0)
    setLineStyleForBorder(border);

  // Box size
  const double width = rect->x2 - rect->x1;
  const double height = rect->y2 - rect->y1;

  // Parse some properties from the appearance string
  double fontsize;
  AnnotColor *fontcolor;
  parseAppearanceString(appearanceString, fontsize, fontcolor);
  // Default values
  if (fontsize <= 0)
    fontsize = 10;
  if (fontcolor == NULL)
    fontcolor = new AnnotColor(0, 0, 0); // Black
  if (!contents)
    contents = new GooString ();

  // Draw box
  GBool doFill = (color && color->getSpace() != AnnotColor::colorTransparent);
  GBool doStroke = (borderWidth != 0);
  if (doFill || doStroke) {
    if (doStroke) {
      setColor(fontcolor, gFalse); // Border color: same as font color
    }
    appearBuf->appendf ("{0:.2f} {0:.2f} {1:.2f} {2:.2f} re\n", borderWidth/2, width-borderWidth, height-borderWidth);
    if (doFill) {
      setColor(color, gTrue);
      appearBuf->append(doStroke ? "B\n" : "f\n");
    } else {
      appearBuf->append("S\n");
    }
  }

  // Setup text clipping
  const double textmargin = borderWidth * 2;
  const double textwidth = width - 2*textmargin;
  appearBuf->appendf ("{0:.2f} {0:.2f} {1:.2f} {2:.2f} re W n\n", textmargin, textwidth, height - 2*textmargin);

  Object fontResDict;
  GfxFont *font = createAnnotDrawFont(xref, &fontResDict);

  // Set font state
  setColor(fontcolor, gTrue);
  appearBuf->appendf ("BT 1 0 0 1 {0:.2f} {1:.2f} Tm\n", textmargin, height - textmargin - fontsize * font->getDescent());
  appearBuf->appendf ("/AnnotDrawFont {0:.2f} Tf\n", fontsize);

  int i = 0;
  double xposPrev = 0;
  while (i < contents->getLength()) {
    GooString out;
    double linewidth, xpos;
    layoutText(contents, &out, &i, font, &linewidth, textwidth/fontsize, NULL, gFalse);
    linewidth *= fontsize;
    switch (quadding) {
    case quaddingCentered:
      xpos = (textwidth - linewidth) / 2;
      break;
    case quaddingRightJustified:
      xpos = textwidth - linewidth;
      break;
    default: // quaddingLeftJustified:
      xpos = 0;
      break;
    }
    appearBuf->appendf("{0:.2f} {1:.2f} Td\n", xpos - xposPrev, -fontsize);
    writeString(&out, appearBuf);
    appearBuf->append("Tj\n");
    xposPrev = xpos;
  }

  font->decRefCnt();
  delete fontcolor;
  appearBuf->append ("ET Q\n");

  double bbox[4];
  bbox[0] = bbox[1] = 0;
  bbox[2] = rect->x2 - rect->x1;
  bbox[3] = rect->y2 - rect->y1;

  if (ca == 1) {
    createForm(bbox, gFalse, &fontResDict, &appearance);
  } else {
    Object aStream, resDict;

    createForm(bbox, gTrue, &fontResDict, &aStream);
    delete appearBuf;

    appearBuf = new GooString ("/GS0 gs\n/Fm0 Do");
    createResourcesDict("Fm0", &aStream, "GS0", ca, NULL, &resDict);
    createForm(bbox, gFalse, &resDict, &appearance);
  }
  delete appearBuf;
}

void AnnotFreeText::draw(Gfx *gfx, GBool printing) {
  Object obj;

  if (!isVisible (printing))
    return;

  annotLocker();
  if (appearance.isNull()) {
    generateFreeTextAppearance();
  }

  // draw the appearance stream
  appearance.fetch(gfx->getXRef(), &obj);
  gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color,
                 rect->x1, rect->y1, rect->x2, rect->y2, getRotation());
  obj.free();
}

// Before retrieving the res dict, regenerate the appearance stream if needed,
// because AnnotFreeText::draw needs to store font info in the res dict
Object *AnnotFreeText::getAppearanceResDict(Object *dest) {
  if (appearance.isNull()) {
    generateFreeTextAppearance();
  }
  return Annot::getAppearanceResDict(dest);
}

//------------------------------------------------------------------------
// AnnotLine
//------------------------------------------------------------------------

AnnotLine::AnnotLine(PDFDoc *docA, PDFRectangle *rect) :
    AnnotMarkup(docA, rect) {
  Object obj1;

  type = typeLine;
  annotObj.dictSet ("Subtype", obj1.initName ("Line"));

  initialize (docA, annotObj.getDict());
}

AnnotLine::AnnotLine(PDFDoc *docA, Dict *dict, Object *obj) :
    AnnotMarkup(docA, dict, obj) {
  type = typeLine;
  initialize(docA, dict);
}

AnnotLine::~AnnotLine() {
  delete coord1;
  delete coord2;

  if (interiorColor)
    delete interiorColor;

  if (measure)
    delete measure;
}

void AnnotLine::initialize(PDFDoc *docA, Dict *dict) {
  Object obj1;

  if (dict->lookup("L", &obj1)->isArray() && obj1.arrayGetLength() == 4) {
    Object obj2;
    double x1, y1, x2, y2;

    (obj1.arrayGet(0, &obj2)->isNum() ? x1 = obj2.getNum() : x1 = 0);
    obj2.free();
    (obj1.arrayGet(1, &obj2)->isNum() ? y1 = obj2.getNum() : y1 = 0);
    obj2.free();
    (obj1.arrayGet(2, &obj2)->isNum() ? x2 = obj2.getNum() : x2 = 0);
    obj2.free();
    (obj1.arrayGet(3, &obj2)->isNum() ? y2 = obj2.getNum() : y2 = 0);
    obj2.free();

    coord1 = new AnnotCoord(x1, y1);
    coord2 = new AnnotCoord(x2, y2);
  } else {
    coord1 = new AnnotCoord();
    coord2 = new AnnotCoord();
  }
  obj1.free();

  if (dict->lookup("LE", &obj1)->isArray() && obj1.arrayGetLength() == 2) {
    Object obj2;

    if(obj1.arrayGet(0, &obj2)->isString())
      startStyle = parseAnnotLineEndingStyle(obj2.getString());
    else
      startStyle = annotLineEndingNone;
    obj2.free();

    if(obj1.arrayGet(1, &obj2)->isString())
      endStyle = parseAnnotLineEndingStyle(obj2.getString());
    else
      endStyle = annotLineEndingNone;
    obj2.free();

  } else {
    startStyle = endStyle = annotLineEndingNone;
  }
  obj1.free();

  if (dict->lookup("IC", &obj1)->isArray()) {
    interiorColor = new AnnotColor(obj1.getArray());
  } else {
    interiorColor = NULL;
  }
  obj1.free();

  if (dict->lookup("LL", &obj1)->isNum()) {
    leaderLineLength = obj1.getNum();
  } else {
    leaderLineLength = 0;
  }
  obj1.free();

  if (dict->lookup("LLE", &obj1)->isNum()) {
    leaderLineExtension = obj1.getNum();

    if (leaderLineExtension < 0)
      leaderLineExtension = 0;
  } else {
    leaderLineExtension = 0;
  }
  obj1.free();

  if (dict->lookup("Cap", &obj1)->isBool()) {
    caption = obj1.getBool();
  } else {
    caption = gFalse;
  }
  obj1.free();

  if (dict->lookup("IT", &obj1)->isName()) {
    const char *intentName = obj1.getName();

    if(!strcmp(intentName, "LineArrow")) {
      intent = intentLineArrow;
    } else if(!strcmp(intentName, "LineDimension")) {
      intent = intentLineDimension;
    } else {
      intent = intentLineArrow;
    }
  } else {
    intent = intentLineArrow;
  }
  obj1.free();

  if (dict->lookup("LLO", &obj1)->isNum()) {
    leaderLineOffset = obj1.getNum();

    if (leaderLineOffset < 0)
      leaderLineOffset = 0;
  } else {
    leaderLineOffset = 0;
  }
  obj1.free();

  if (dict->lookup("CP", &obj1)->isName()) {
    const char *captionName = obj1.getName();

    if(!strcmp(captionName, "Inline")) {
      captionPos = captionPosInline;
    } else if(!strcmp(captionName, "Top")) {
      captionPos = captionPosTop;
    } else {
      captionPos = captionPosInline;
    }
  } else {
    captionPos = captionPosInline;
  }
  obj1.free();

  if (dict->lookup("Measure", &obj1)->isDict()) {
    measure = NULL;
  } else {
    measure = NULL;
  }
  obj1.free();

  if ((dict->lookup("CO", &obj1)->isArray()) && (obj1.arrayGetLength() == 2)) {
    Object obj2;

    (obj1.arrayGet(0, &obj2)->isNum() ? captionTextHorizontal = obj2.getNum() :
      captionTextHorizontal = 0);
    obj2.free();
    (obj1.arrayGet(1, &obj2)->isNum() ? captionTextVertical = obj2.getNum() :
      captionTextVertical = 0);
    obj2.free();
  } else {
    captionTextHorizontal = captionTextVertical = 0;
  }
  obj1.free();

  if (dict->lookup("BS", &obj1)->isDict()) {
    delete border;
    border = new AnnotBorderBS(obj1.getDict());
  } else if (!border) {
    border = new AnnotBorderBS();
  }
  obj1.free();
}

void AnnotLine::setContents(GooString *new_content) {
  Annot::setContents(new_content);
  if (caption)
    invalidateAppearance();
}

void AnnotLine::setVertices(double x1, double y1, double x2, double y2) {
  Object obj1, obj2;

  delete coord1;
  coord1 = new AnnotCoord(x1, y1);
  delete coord2;
  coord2 = new AnnotCoord(x2, y2);

  obj1.initArray(xref);
  obj1.arrayAdd( obj2.initReal(x1) );
  obj1.arrayAdd( obj2.initReal(y1) );
  obj1.arrayAdd( obj2.initReal(x2) );
  obj1.arrayAdd( obj2.initReal(y2) );

  update("L", &obj1);
  invalidateAppearance();
}

void AnnotLine::setStartEndStyle(AnnotLineEndingStyle start, AnnotLineEndingStyle end) {
  Object obj1, obj2;

  startStyle = start;
  endStyle = end;

  obj1.initArray(xref);
  obj1.arrayAdd( obj2.initName(convertAnnotLineEndingStyle( startStyle )) );
  obj1.arrayAdd( obj2.initName(convertAnnotLineEndingStyle( endStyle )) );

  update("LE", &obj1);
  invalidateAppearance();
}

void AnnotLine::setInteriorColor(AnnotColor *new_color) {
  delete interiorColor;

  if (new_color) {
    Object obj1;
    new_color->writeToObject(xref, &obj1);
    update ("IC", &obj1);
    interiorColor = new_color;
  } else {
    interiorColor = NULL;
  }
  invalidateAppearance();
}

void AnnotLine::setLeaderLineLength(double len) {
  Object obj1;

  leaderLineLength = len;
  obj1.initReal(len);
  update ("LL", &obj1);
  invalidateAppearance();
}

void AnnotLine::setLeaderLineExtension(double len) {
  Object obj1;

  leaderLineExtension = len;
  obj1.initReal(len);
  update ("LLE", &obj1);

  // LL is required if LLE is present
  obj1.initReal(leaderLineLength);
  update ("LL", &obj1);
  invalidateAppearance();
}

void AnnotLine::setCaption(bool new_cap) {
  Object obj1;

  caption = new_cap;
  obj1.initBool(new_cap);
  update ("Cap", &obj1);
  invalidateAppearance();
}

void AnnotLine::setIntent(AnnotLineIntent new_intent) {
  Object obj1;

  intent = new_intent;
  if (new_intent == intentLineArrow)
    obj1.initName("LineArrow");
  else // intentLineDimension
    obj1.initName("LineDimension");
  update ("IT", &obj1);
}

void AnnotLine::generateLineAppearance()
{
  double borderWidth, ca = opacity;

  appearBBox = new AnnotAppearanceBBox(rect);
  appearBuf = new GooString ();
  appearBuf->append ("q\n");
  if (color) {
    setColor(color, gFalse);
  }

  setLineStyleForBorder(border);
  borderWidth = border->getWidth();
  appearBBox->setBorderWidth(std::max(1., borderWidth));

  const double x1 = coord1->getX();
  const double y1 = coord1->getY();
  const double x2 = coord2->getX();
  const double y2 = coord2->getY();

  // Main segment length
  const double main_len = sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1));

  // Main segment becomes positive x direction, coord1 becomes (0,0)
  Matrix matr;
  const double angle = atan2(y2 - y1, x2 - x1);
  matr.m[0] = matr.m[3] = cos(angle);
  matr.m[1] = sin(angle);
  matr.m[2] = -matr.m[1];
  matr.m[4] = x1-rect->x1;
  matr.m[5] = y1-rect->y1;

  double tx, ty, captionwidth = 0, captionheight = 0;
  AnnotLineCaptionPos actualCaptionPos = captionPos;
  const double fontsize = 9;
  const double captionhmargin = 2; // Left and right margin (inline caption only)
  const double captionmaxwidth = main_len - 2 * captionhmargin;

  Object fontResDict;
  GfxFont *font;

  // Calculate caption width and height
  if (caption) {
    font = createAnnotDrawFont(xref, &fontResDict);
    int lines = 0;
    int i = 0;
    while (i < contents->getLength()) {
      GooString out;
      double linewidth;
      layoutText(contents, &out, &i, font, &linewidth, 0, NULL, gFalse);
      linewidth *= fontsize;
      if (linewidth > captionwidth) {
        captionwidth = linewidth;
      }
      ++lines;
    }
    captionheight = lines * fontsize;
    // If text is longer than available space, turn into captionPosTop
    if (captionwidth > captionmaxwidth) {
      actualCaptionPos = captionPosTop;
    }
  } else {
    fontResDict.initNull();
    font = NULL;
  }

  // Draw main segment
  matr.transform (0, leaderLineLength, &tx, &ty);
  appearBuf->appendf ("{0:.2f} {1:.2f} m\n", tx, ty);
  appearBBox->extendTo (tx, ty);

  if (captionwidth != 0 && actualCaptionPos == captionPosInline) { // Break in the middle
    matr.transform ((main_len-captionwidth)/2 - captionhmargin, leaderLineLength, &tx, &ty);
    appearBuf->appendf ("{0:.2f} {1:.2f} l S\n", tx, ty);

    matr.transform ((main_len+captionwidth)/2 + captionhmargin, leaderLineLength, &tx, &ty);
    appearBuf->appendf ("{0:.2f} {1:.2f} m\n", tx, ty);
  }

  matr.transform (main_len, leaderLineLength, &tx, &ty);
  appearBuf->appendf ("{0:.2f} {1:.2f} l S\n", tx, ty);
  appearBBox->extendTo (tx, ty);

  // TODO: Line endings

  // Draw caption text
  if (caption) {
    double tlx = (main_len - captionwidth) / 2, tly; // Top-left coords
    if (actualCaptionPos == captionPosInline) {
      tly = leaderLineLength + captionheight / 2;
    } else {
      tly = leaderLineLength + captionheight + 2*borderWidth;
    }

    tlx += captionTextHorizontal;
    tly += captionTextVertical;

    // Adjust bounding box
    matr.transform (tlx, tly-captionheight, &tx, &ty);
    appearBBox->extendTo (tx, ty);
    matr.transform (tlx+captionwidth, tly-captionheight, &tx, &ty);
    appearBBox->extendTo (tx, ty);
    matr.transform (tlx+captionwidth, tly, &tx, &ty);
    appearBBox->extendTo (tx, ty);
    matr.transform (tlx, tly, &tx, &ty);
    appearBBox->extendTo (tx, ty);

    // Setup text state (reusing transformed top-left coord)
    appearBuf->appendf ("0 g BT /AnnotDrawFont {0:.2f} Tf\n", fontsize); // Font color: black
    appearBuf->appendf ("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} Tm\n",
                        matr.m[0], matr.m[1], matr.m[2], matr.m[3], tx, ty);
    appearBuf->appendf ("0 {0:.2f} Td\n",  -fontsize * font->getDescent());
    // Draw text
    int i = 0;
    double xposPrev = 0;
    while (i < contents->getLength()) {
      GooString out;
      double linewidth, xpos;
      layoutText(contents, &out, &i, font, &linewidth, 0, NULL, gFalse);
      linewidth *= fontsize;
      xpos = (captionwidth - linewidth) / 2;
      appearBuf->appendf("{0:.2f} {1:.2f} Td\n", xpos - xposPrev, -fontsize);
      writeString(&out, appearBuf);
      appearBuf->append ("Tj\n");
      xposPrev = xpos;
    }
    appearBuf->append ("ET\n");
    font->decRefCnt();
  }

  // Draw leader lines
  double ll_len = fabs(leaderLineLength) + leaderLineExtension;
  double sign = leaderLineLength >= 0 ? 1 : -1;
  if (ll_len != 0) {
    matr.transform (0, 0, &tx, &ty);
    appearBuf->appendf ("{0:.2f} {1:.2f} m\n", tx, ty);
    appearBBox->extendTo (tx, ty);
    matr.transform (0, sign*ll_len, &tx, &ty);
    appearBuf->appendf ("{0:.2f} {1:.2f} l S\n", tx, ty);
    appearBBox->extendTo (tx, ty);

    matr.transform (main_len, 0, &tx, &ty);
    appearBuf->appendf ("{0:.2f} {1:.2f} m\n", tx, ty);
    appearBBox->extendTo (tx, ty);
    matr.transform (main_len, sign*ll_len, &tx, &ty);
    appearBuf->appendf ("{0:.2f} {1:.2f} l S\n", tx, ty);
    appearBBox->extendTo (tx, ty);
  }

  appearBuf->append ("Q\n");

  double bbox[4];
  appearBBox->getBBoxRect(bbox);
  if (ca == 1) {
    createForm(bbox, gFalse, &fontResDict, &appearance);
  } else {
    Object aStream, resDict;

    createForm(bbox, gTrue, &fontResDict, &aStream);
    delete appearBuf;

    appearBuf = new GooString ("/GS0 gs\n/Fm0 Do");
    createResourcesDict("Fm0", &aStream, "GS0", ca, NULL, &resDict);
    createForm(bbox, gFalse, &resDict, &appearance);
  }
  delete appearBuf;
}

void AnnotLine::draw(Gfx *gfx, GBool printing) {
  Object obj;

  if (!isVisible (printing))
    return;

  annotLocker();
  if (appearance.isNull()) {
    generateLineAppearance();
  }

  // draw the appearance stream
  appearance.fetch(gfx->getXRef(), &obj);
  if (appearBBox) {
    gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color,
                   appearBBox->getPageXMin(), appearBBox->getPageYMin(),
                   appearBBox->getPageXMax(), appearBBox->getPageYMax(),
                   getRotation());
  } else {
    gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color,
                   rect->x1, rect->y1, rect->x2, rect->y2, getRotation());
  }
  obj.free();
}

// Before retrieving the res dict, regenerate the appearance stream if needed,
// because AnnotLine::draw may need to store font info in the res dict
Object *AnnotLine::getAppearanceResDict(Object *dest) {
  if (appearance.isNull()) {
    generateLineAppearance();
  }
  return Annot::getAppearanceResDict(dest);
}

//------------------------------------------------------------------------
// AnnotTextMarkup
//------------------------------------------------------------------------
AnnotTextMarkup::AnnotTextMarkup(PDFDoc *docA, PDFRectangle *rect, AnnotSubtype subType) :
    AnnotMarkup(docA, rect) {
  Object obj1;

  switch (subType) {
    case typeHighlight:
      annotObj.dictSet ("Subtype", obj1.initName ("Highlight"));
      break;
    case typeUnderline:
      annotObj.dictSet ("Subtype", obj1.initName ("Underline"));
      break;
    case typeSquiggly:
      annotObj.dictSet ("Subtype", obj1.initName ("Squiggly"));
      break;
    case typeStrikeOut:
      annotObj.dictSet ("Subtype", obj1.initName ("StrikeOut"));
      break;
    default:
      assert (0 && "Invalid subtype for AnnotTextMarkup\n");
  }

  // Store dummy quadrilateral with null coordinates
  Object obj2, obj3;
  obj2.initArray (doc->getXRef());
  for (int i = 0; i < 4*2; ++i) {
    obj2.arrayAdd (obj3.initReal (0));
  }
  annotObj.dictSet ("QuadPoints", &obj2);

  initialize(docA, annotObj.getDict());
}

AnnotTextMarkup::AnnotTextMarkup(PDFDoc *docA, Dict *dict, Object *obj) :
  AnnotMarkup(docA, dict, obj) {
  // the real type will be read in initialize()
  type = typeHighlight;
  initialize(docA, dict);
}

void AnnotTextMarkup::initialize(PDFDoc *docA, Dict *dict) {
  Object obj1;

  if (dict->lookup("Subtype", &obj1)->isName()) {
    GooString typeName(obj1.getName());
    if (!typeName.cmp("Highlight")) {
      type = typeHighlight;
    } else if (!typeName.cmp("Underline")) {
      type = typeUnderline;
    } else if (!typeName.cmp("Squiggly")) {
      type = typeSquiggly;
    } else if (!typeName.cmp("StrikeOut")) {
      type = typeStrikeOut;
    }
  }
  obj1.free();

  if(dict->lookup("QuadPoints", &obj1)->isArray()) {
    quadrilaterals = new AnnotQuadrilaterals(obj1.getArray(), rect);
  } else {
    error(errSyntaxError, -1, "Bad Annot Text Markup QuadPoints");
    quadrilaterals = NULL;
    ok = gFalse;
  }
  obj1.free();
}

AnnotTextMarkup::~AnnotTextMarkup() {
  if(quadrilaterals) {
    delete quadrilaterals;
  }
}

void AnnotTextMarkup::setType(AnnotSubtype new_type) {
  Object obj1;

  switch (new_type) {
    case typeHighlight:
      obj1.initName("Highlight");
      break;
    case typeUnderline:
      obj1.initName("Underline");
      break;
    case typeSquiggly:
      obj1.initName("Squiggly");
      break;
    case typeStrikeOut:
      obj1.initName("StrikeOut");
      break;
    default:
      assert(!"Invalid subtype");
  }

  type = new_type;
  update("Subtype", &obj1);
  invalidateAppearance();
}

void AnnotTextMarkup::setQuadrilaterals(AnnotQuadrilaterals *quadPoints) {
  Object obj1, obj2;
  obj1.initArray (xref);

  for (int i = 0; i < quadPoints->getQuadrilateralsLength(); ++i) {
    obj1.arrayAdd (obj2.initReal (quadPoints->getX1(i)));
    obj1.arrayAdd (obj2.initReal (quadPoints->getY1(i)));
    obj1.arrayAdd (obj2.initReal (quadPoints->getX2(i)));
    obj1.arrayAdd (obj2.initReal (quadPoints->getY2(i)));
    obj1.arrayAdd (obj2.initReal (quadPoints->getX3(i)));
    obj1.arrayAdd (obj2.initReal (quadPoints->getY3(i)));
    obj1.arrayAdd (obj2.initReal (quadPoints->getX4(i)));
    obj1.arrayAdd (obj2.initReal (quadPoints->getY4(i)));
  }

  delete quadrilaterals;
  quadrilaterals = new AnnotQuadrilaterals(obj1.getArray(), rect);

  annotObj.dictSet ("QuadPoints", &obj1);
  invalidateAppearance();
}

void AnnotTextMarkup::draw(Gfx *gfx, GBool printing) {
  Object obj;
  double ca = 1;
  int i;
  Object obj1, obj2;

  if (!isVisible (printing))
    return;

  annotLocker();
  if (appearance.isNull() || type == typeHighlight) {
    GBool blendMultiply = gTrue;
    ca = opacity;

    appearBuf = new GooString ();
    appearBuf->append ("q\n");

    /* Adjust BBox */
    delete appearBBox;
    appearBBox = new AnnotAppearanceBBox(rect);
    for (i = 0; i < quadrilaterals->getQuadrilateralsLength(); ++i) {
      appearBBox->extendTo (quadrilaterals->getX1(i) - rect->x1, quadrilaterals->getY1(i) - rect->y1);
      appearBBox->extendTo (quadrilaterals->getX2(i) - rect->x1, quadrilaterals->getY2(i) - rect->y1);
      appearBBox->extendTo (quadrilaterals->getX3(i) - rect->x1, quadrilaterals->getY3(i) - rect->y1);
      appearBBox->extendTo (quadrilaterals->getX4(i) - rect->x1, quadrilaterals->getY4(i) - rect->y1);
    }

    switch (type) {
    case typeUnderline:
      if (color) {
        setColor(color, gFalse);
      }
      appearBuf->append ("[] 0 d 1 w\n");

      for (i = 0; i < quadrilaterals->getQuadrilateralsLength(); ++i) {
	double x3, y3, x4, y4;

	x3 = quadrilaterals->getX3(i);
	y3 = quadrilaterals->getY3(i);
	x4 = quadrilaterals->getX4(i);
	y4 = quadrilaterals->getY4(i);

	appearBuf->appendf ("{0:.2f} {1:.2f} m\n", x3, y3);
	appearBuf->appendf ("{0:.2f} {1:.2f} l\n", x4, y4);
	appearBuf->append ("S\n");
      }
      break;
    case typeStrikeOut:
      if (color) {
        setColor(color, gFalse);
      }
      blendMultiply = gFalse;
      appearBuf->append ("[] 0 d 1 w\n");

      for (i = 0; i < quadrilaterals->getQuadrilateralsLength(); ++i) {
	double x1, y1, x2, y2;
	double x3, y3, x4, y4;

	x1 = quadrilaterals->getX1(i);
	y1 = quadrilaterals->getY1(i);
	x2 = quadrilaterals->getX2(i);
	y2 = quadrilaterals->getY2(i);

	x3 = quadrilaterals->getX3(i);
	y3 = quadrilaterals->getY3(i);
	x4 = quadrilaterals->getX4(i);
	y4 = quadrilaterals->getY4(i);

	appearBuf->appendf ("{0:.2f} {1:.2f} m\n", (x1+x3)/2., (y1+y3)/2.);
	appearBuf->appendf ("{0:.2f} {1:.2f} l\n", (x2+x4)/2., (y2+y4)/2.);
	appearBuf->append ("S\n");
      }
      break;
    case typeSquiggly:
      if (color) {
        setColor(color, gFalse);
      }
      appearBuf->append ("[] 0 d 1 w\n");

      for (i = 0; i < quadrilaterals->getQuadrilateralsLength(); ++i) {
        double x1, y1, x2, y3;
        double h6;

        x1 = quadrilaterals->getX1(i);
        y1 = quadrilaterals->getY1(i);
        x2 = quadrilaterals->getX2(i);
        y3 = quadrilaterals->getY3(i);
        h6 = (y1 - y3) / 6.0;

        appearBuf->appendf ("{0:.2f} {1:.2f} m\n", x1, y3+h6);
        bool down = false;
        do {
          down = !down; // Zigzag line
          x1 += 2;
          appearBuf->appendf ("{0:.2f} {1:.2f} l\n", x1, y3 + (down ? 0 : h6));
        } while (x1 < x2);
        appearBuf->append ("S\n");
      }
      break;
    default:
    case typeHighlight:
      appearance.free();

      if (color)
        setColor(color, gTrue);

      double biggestBorder = 0;
      for (i = 0; i < quadrilaterals->getQuadrilateralsLength(); ++i) {
        double x1, y1, x2, y2, x3, y3, x4, y4;
	double h4;

	x1 = quadrilaterals->getX1(i);
	y1 = quadrilaterals->getY1(i);
	x2 = quadrilaterals->getX2(i);
	y2 = quadrilaterals->getY2(i);
	x3 = quadrilaterals->getX3(i);
	y3 = quadrilaterals->getY3(i);
	x4 = quadrilaterals->getX4(i);
	y4 = quadrilaterals->getY4(i);
	h4 = fabs(y1 - y3) / 4.0;

	if (h4 > biggestBorder) {
	  biggestBorder = h4;
	}

	appearBuf->appendf ("{0:.2f} {1:.2f} m\n", x3, y3);
	appearBuf->appendf ("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
			    x3 - h4, y3 + h4, x1 - h4, y1 - h4, x1, y1);
	appearBuf->appendf ("{0:.2f} {1:.2f} l\n", x2, y2);
	appearBuf->appendf ("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
			    x2 + h4, y2 - h4, x4 + h4, y4 + h4, x4, y4);
	appearBuf->append ("f\n");
      }
      appearBBox->setBorderWidth(biggestBorder);
      break;
    }
    appearBuf->append ("Q\n");

    Object aStream, resDict;
    double bbox[4];
    bbox[0] = appearBBox->getPageXMin();
    bbox[1] = appearBBox->getPageYMin();
    bbox[2] = appearBBox->getPageXMax();
    bbox[3] = appearBBox->getPageYMax();
    createForm(bbox, gTrue, NULL, &aStream);
    delete appearBuf;

    appearBuf = new GooString ("/GS0 gs\n/Fm0 Do");
    createResourcesDict("Fm0", &aStream, "GS0", 1, blendMultiply ? "Multiply" : NULL, &resDict);
    if (ca == 1) {
      createForm(bbox, gFalse, &resDict, &appearance);
    } else {
      createForm(bbox, gTrue, &resDict, &aStream);
      delete appearBuf;

      appearBuf = new GooString ("/GS0 gs\n/Fm0 Do");
      createResourcesDict("Fm0", &aStream, "GS0", ca, NULL, &resDict);
      createForm(bbox, gFalse, &resDict, &appearance);
    }
    delete appearBuf;
  }

  // draw the appearance stream
  appearance.fetch(gfx->getXRef(), &obj);
  if (appearBBox) {
    gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color,
                   appearBBox->getPageXMin(), appearBBox->getPageYMin(),
                   appearBBox->getPageXMax(), appearBBox->getPageYMax(),
                   getRotation());
  } else {
    gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color,
                   rect->x1, rect->y1, rect->x2, rect->y2, getRotation());
  }
  obj.free();
}

//------------------------------------------------------------------------
// AnnotWidget
//------------------------------------------------------------------------

AnnotWidget::AnnotWidget(PDFDoc *docA, Dict *dict, Object *obj) :
    Annot(docA, dict, obj) {
  type = typeWidget;
  field = NULL;
  initialize(docA, dict);
}

AnnotWidget::AnnotWidget(PDFDoc *docA, Dict *dict, Object *obj, FormField *fieldA) :
    Annot(docA, dict, obj) {
  type = typeWidget;
  field = fieldA;
  initialize(docA, dict);
}

AnnotWidget::~AnnotWidget() {
  if (appearCharacs)
    delete appearCharacs;
  
  if (action)
    delete action;

  additionalActions.free();

  if (parent)
    delete parent;
}

void AnnotWidget::initialize(PDFDoc *docA, Dict *dict) {
  Object obj1;

  form = doc->getCatalog()->getForm();

  if(dict->lookup("H", &obj1)->isName()) {
    const char *modeName = obj1.getName();

    if(!strcmp(modeName, "N")) {
      mode = highlightModeNone;
    } else if(!strcmp(modeName, "O")) {
      mode = highlightModeOutline;
    } else if(!strcmp(modeName, "P") || !strcmp(modeName, "T")) {
      mode = highlightModePush;
    } else {
      mode = highlightModeInvert;
    }
  } else {
    mode = highlightModeInvert;
  }
  obj1.free();

  if(dict->lookup("MK", &obj1)->isDict()) {
    appearCharacs = new AnnotAppearanceCharacs(obj1.getDict());
  } else {
    appearCharacs = NULL;
  }
  obj1.free();

  action = NULL;
  if(dict->lookup("A", &obj1)->isDict()) {
    action = LinkAction::parseAction(&obj1, doc->getCatalog()->getBaseURI());
  }
  obj1.free();

  dict->lookupNF("AA", &additionalActions);

  if(dict->lookup("Parent", &obj1)->isDict()) {
    parent = NULL;
  } else {
    parent = NULL;
  }
  obj1.free();

  if (dict->lookup("BS", &obj1)->isDict()) {
    delete border;
    border = new AnnotBorderBS(obj1.getDict());
  }
  obj1.free();

  updatedAppearanceStream.num = updatedAppearanceStream.gen = -1;
}

LinkAction* AnnotWidget::getAdditionalAction(AdditionalActionsType type)
{
  return ::getAdditionalAction(type, &additionalActions, doc);
}

LinkAction* AnnotWidget::getFormAdditionalAction(FormAdditionalActionsType type)
{
  return ::getFormAdditionalAction(type, &additionalActions, doc);
}

// Grand unified handler for preparing text strings to be drawn into form
// fields.  Takes as input a text string (in PDFDocEncoding or UTF-16).
// Converts some or all of this string to the appropriate encoding for the
// specified font, and computes the width of the text.  Can optionally stop
// converting when a specified width has been reached, to perform line-breaking
// for multi-line fields.
//
// Parameters:
//   text: input text string to convert
//   outBuf: buffer for writing re-encoded string
//   i: index at which to start converting; will be updated to point just after
//      last character processed
//   font: the font which will be used for display
//   width: computed width (unscaled by font size) will be stored here
//   widthLimit: if non-zero, stop converting to keep width under this value
//      (should be scaled down by font size)
//   charCount: count of number of characters will be stored here
//   noReencode: if set, do not try to translate the character encoding
//      (useful for Zapf Dingbats or other unusual encodings)
//      can only be used with simple fonts, not CID-keyed fonts
//
// TODO: Handle surrogate pairs in UTF-16.
//       Should be able to generate output for any CID-keyed font.
//       Doesn't handle vertical fonts--should it?
void Annot::layoutText(GooString *text, GooString *outBuf, int *i,
                             GfxFont *font, double *width, double widthLimit,
                             int *charCount, GBool noReencode)
{
  CharCode c;
  Unicode uChar, *uAux;
  double w = 0.0;
  int uLen, n;
  double dx, dy, ox, oy;
  if (!text) {
    return;
  }
  GBool unicode = text->hasUnicodeMarker();
  GBool spacePrev;              // previous character was a space

  // State for backtracking when more text has been processed than fits within
  // widthLimit.  We track for both input (text) and output (outBuf) the offset
  // to the first character to discard.
  //
  // We keep track of several points:
  //   1 - end of previous completed word which fits
  //   2 - previous character which fit
  int last_i1, last_i2, last_o1, last_o2;

  if (unicode && text->getLength() % 2 != 0) {
    error(errSyntaxError, -1, "AnnotWidget::layoutText, bad unicode string");
    return;
  }

  // skip Unicode marker on string if needed
  if (unicode && *i == 0)
    *i = 2;

  // Start decoding and copying characters, until either:
  //   we reach the end of the string
  //   we reach the maximum width
  //   we reach a newline character
  // As we copy characters, keep track of the last full word to fit, so that we
  // can backtrack if we exceed the maximum width.
  last_i1 = last_i2 = *i;
  last_o1 = last_o2 = 0;
  spacePrev = gFalse;
  outBuf->clear();

  while (*i < text->getLength()) {
    last_i2 = *i;
    last_o2 = outBuf->getLength();

    if (unicode) {
      uChar = (unsigned char)(text->getChar(*i)) << 8;
      uChar += (unsigned char)(text->getChar(*i + 1));
      *i += 2;
    } else {
      if (noReencode)
        uChar = text->getChar(*i) & 0xff;
      else
        uChar = pdfDocEncoding[text->getChar(*i) & 0xff];
      *i += 1;
    }

    // Explicit line break?
    if (uChar == '\r' || uChar == '\n') {
      // Treat a <CR><LF> sequence as a single line break
      if (uChar == '\r' && *i < text->getLength()) {
        if (unicode && text->getChar(*i) == '\0'
            && text->getChar(*i + 1) == '\n')
          *i += 2;
        else if (!unicode && text->getChar(*i) == '\n')
          *i += 1;
      }

      break;
    }

    if (noReencode) {
      outBuf->append(uChar);
    } else {
      CharCodeToUnicode *ccToUnicode = font->getToUnicode();
      if (!ccToUnicode) {
        // This assumes an identity CMap.
        outBuf->append((uChar >> 8) & 0xff);
        outBuf->append(uChar & 0xff);
      } else if (ccToUnicode->mapToCharCode(&uChar, &c, 1)) {
        ccToUnicode->decRefCnt();
        if (font->isCIDFont()) {
          // TODO: This assumes an identity CMap.  It should be extended to
          // handle the general case.
          outBuf->append((c >> 8) & 0xff);
          outBuf->append(c & 0xff);
        } else {
          // 8-bit font
          outBuf->append(c);
        }
      } else {
        ccToUnicode->decRefCnt();
        error(errSyntaxError, -1, "AnnotWidget::layoutText, cannot convert U+{0:04uX}", uChar);
      }
    }

    // If we see a space, then we have a linebreak opportunity.
    if (uChar == ' ') {
      last_i1 = *i;
      if (!spacePrev)
        last_o1 = last_o2;
      spacePrev = gTrue;
    } else {
      spacePrev = gFalse;
    }

    // Compute width of character just output
    if (outBuf->getLength() > last_o2) {
      dx = 0.0;
      font->getNextChar(outBuf->getCString() + last_o2,
                        outBuf->getLength() - last_o2,
                        &c, &uAux, &uLen, &dx, &dy, &ox, &oy);
      w += dx;
    }

    // Current line over-full now?
    if (widthLimit > 0.0 && w > widthLimit) {
      if (last_o1 > 0) {
        // Back up to the previous word which fit, if there was a previous
        // word.
        *i = last_i1;
        outBuf->del(last_o1, outBuf->getLength() - last_o1);
      } else if (last_o2 > 0) {
        // Otherwise, back up to the previous character (in the only word on
        // this line)
        *i = last_i2;
        outBuf->del(last_o2, outBuf->getLength() - last_o2);
      } else {
        // Otherwise, we were trying to fit the first character; include it
        // anyway even if it overflows the space--no updates to make.
      }
      break;
    }
  }

  // If splitting input into lines because we reached the width limit, then
  // consume any remaining trailing spaces that would go on this line from the
  // input.  If in doing so we reach a newline, consume that also.  This code
  // won't run if we stopped at a newline above, since in that case w <=
  // widthLimit still.
  if (widthLimit > 0.0 && w > widthLimit) {
    if (unicode) {
      while (*i < text->getLength()
             && text->getChar(*i) == '\0' && text->getChar(*i + 1) == ' ')
        *i += 2;
      if (*i < text->getLength()
          && text->getChar(*i) == '\0' && text->getChar(*i + 1) == '\r')
        *i += 2;
      if (*i < text->getLength()
          && text->getChar(*i) == '\0' && text->getChar(*i + 1) == '\n')
        *i += 2;
    } else {
      while (*i < text->getLength() && text->getChar(*i) == ' ')
        *i += 1;
      if (*i < text->getLength() && text->getChar(*i) == '\r')
        *i += 1;
      if (*i < text->getLength() && text->getChar(*i) == '\n')
        *i += 1;
    }
  }

  // Compute the actual width and character count of the final string, based on
  // breakpoint, if this information is requested by the caller.
  if (width != NULL || charCount != NULL) {
    char *s = outBuf->getCString();
    int len = outBuf->getLength();

    if (width != NULL)
      *width = 0.0;
    if (charCount != NULL)
      *charCount = 0;

    while (len > 0) {
      dx = 0.0;
      n = font->getNextChar(s, len, &c, &uAux, &uLen, &dx, &dy, &ox, &oy);

      if (n == 0) {
        break;
      }

      if (width != NULL)
        *width += dx;
      if (charCount != NULL)
        *charCount += 1;

      s += n;
      len -= n;
    }
  }
}

// Copy the given string to appearBuf, adding parentheses around it and
// escaping characters as appropriate.
void Annot::writeString(GooString *str, GooString *appearBuf)
{
  char c;
  int i;

  appearBuf->append('(');

  for (i = 0; i < str->getLength(); ++i) {
    c = str->getChar(i);
    if (c == '(' || c == ')' || c == '\\') {
      appearBuf->append('\\');
      appearBuf->append(c);
    } else if (c < 0x20) {
      appearBuf->appendf("\\{0:03o}", (unsigned char)c);
    } else {
      appearBuf->append(c);
    }
  }

  appearBuf->append(')');
}

// Draw the variable text or caption for a field.
void AnnotWidget::drawText(GooString *text, GooString *da, GfxResources *resources,
    GBool multiline, int comb, int quadding,
    GBool txField, GBool forceZapfDingbats,
    GBool password) {
  GooList *daToks;
  GooString *tok, *convertedText;
  GfxFont *font;
  double dx, dy;
  double fontSize, fontSize2, borderWidth, x, xPrev, y, w, wMax;
  int tfPos, tmPos, i, j;
  int rot;
  GBool freeText = gFalse;      // true if text should be freed before return
  GBool freeFont = gFalse;

  //~ if there is no MK entry, this should use the existing content stream,
  //~ and only replace the marked content portion of it
  //~ (this is only relevant for Tx fields)
  
  // parse the default appearance string
  tfPos = tmPos = -1;
  if (da) {
    daToks = new GooList();
    i = 0;
    while (i < da->getLength()) {
      while (i < da->getLength() && Lexer::isSpace(da->getChar(i))) {
        ++i;
      }
      if (i < da->getLength()) {
        for (j = i + 1;
            j < da->getLength() && !Lexer::isSpace(da->getChar(j));
            ++j) ;
        daToks->append(new GooString(da, i, j - i));
        i = j;
      }
    }
    for (i = 2; i < daToks->getLength(); ++i) {
      if (i >= 2 && !((GooString *)daToks->get(i))->cmp("Tf")) {
        tfPos = i - 2;
      } else if (i >= 6 && !((GooString *)daToks->get(i))->cmp("Tm")) {
        tmPos = i - 6;
      }
    }
  } else {
    daToks = NULL;
  }

  // force ZapfDingbats
  if (forceZapfDingbats) {
    if (tfPos >= 0) {
      tok = (GooString *)daToks->get(tfPos);
      if (tok->cmp("/ZaDb")) {
        tok->clear();
        tok->append("/ZaDb");
      }
    }
  }
  // get the font and font size
  font = NULL;
  fontSize = 0;
  if (tfPos >= 0) {
    tok = (GooString *)daToks->get(tfPos);
    if (tok->getLength() >= 1 && tok->getChar(0) == '/') {
      if (!resources || !(font = resources->lookupFont(tok->getCString() + 1))) {
        if (forceZapfDingbats) {
          // We are forcing ZaDb but the font does not exist
          // so create a fake one
          Ref r; // dummy Ref, it's not used at all in this codepath
          r.num = -1;
          r.gen = -1;
          Dict *d = new Dict(xref);
          font = new Gfx8BitFont(xref, "ZaDb", r, new GooString("ZapfDingbats"), fontType1, r, d);
          delete d;
          freeFont = gTrue;
          addDingbatsResource = gTrue;
        } else {
          error(errSyntaxError, -1, "Unknown font in field's DA string");
        }
      }
    } else {
      error(errSyntaxError, -1, "Invalid font name in 'Tf' operator in field's DA string");
    }
    tok = (GooString *)daToks->get(tfPos + 1);
    fontSize = gatof(tok->getCString());
  } else {
    error(errSyntaxError, -1, "Missing 'Tf' operator in field's DA string");
  }
  if (!font) {
    if (daToks) {
      deleteGooList(daToks, GooString);
    }
    return;
  }

  // get the border width
  borderWidth = border ? border->getWidth() : 0;

  // for a password field, replace all characters with asterisks
  if (password) {
    int len;
    if (text->hasUnicodeMarker())
      len = (text->getLength() - 2) / 2;
    else
      len = text->getLength();

    text = new GooString;
    for (i = 0; i < len; ++i)
      text->append('*');
    freeText = gTrue;
  }

  convertedText = new GooString;

  // setup
  if (txField) {
    appearBuf->append("/Tx BMC\n");
  }
  appearBuf->append("q\n");
  rot = appearCharacs ? appearCharacs->getRotation() : 0;
  switch (rot) {
  case 90:
    appearBuf->appendf("0 1 -1 0 {0:.2f} 0 cm\n", rect->x2 - rect->x1);
    dx = rect->y2 - rect->y1;
    dy = rect->x2 - rect->x1;
    break;
  case 180:
    appearBuf->appendf("-1 0 0 -1 {0:.2f} {1:.2f} cm\n",
		       rect->x2 - rect->x1, rect->y2 - rect->y1);
    dx = rect->x2 - rect->y2;
    dy = rect->y2 - rect->y1;
    break;
  case 270:
    appearBuf->appendf("0 -1 1 0 0 {0:.2f} cm\n", rect->y2 - rect->y1);
    dx = rect->y2 - rect->y1;
    dy = rect->x2 - rect->x1;
    break;
  default: // assume rot == 0
    dx = rect->x2 - rect->x1;
    dy = rect->y2 - rect->y1;
    break;
  }
  appearBuf->append("BT\n");
  // multi-line text
  if (multiline) {
    // note: the comb flag is ignored in multiline mode

    wMax = dx - 2 * borderWidth - 4;

    // compute font autosize
    if (fontSize == 0) {
      for (fontSize = 20; fontSize > 1; --fontSize) {
        y = dy - 3;
        i = 0;
        while (i < text->getLength()) {
          layoutText(text, convertedText, &i, font, &w, wMax / fontSize, NULL,
                     forceZapfDingbats);
          y -= fontSize;
        }
        // approximate the descender for the last line
        if (y >= 0.33 * fontSize) {
          break;
        }
      }
      if (tfPos >= 0) {
        tok = (GooString *)daToks->get(tfPos + 1);
        tok->clear();
        tok->appendf("{0:.2f}", fontSize);
      }
    }

    // starting y coordinate
    // (note: each line of text starts with a Td operator that moves
    // down a line)
    y = dy - 3;

    // set the font matrix
    if (tmPos >= 0) {
      tok = (GooString *)daToks->get(tmPos + 4);
      tok->clear();
      tok->append('0');
      tok = (GooString *)daToks->get(tmPos + 5);
      tok->clear();
      tok->appendf("{0:.2f}", y);
    }

    // write the DA string
    if (daToks) {
      for (i = 0; i < daToks->getLength(); ++i) {
        appearBuf->append((GooString *)daToks->get(i))->append(' ');
      }
    }

    // write the font matrix (if not part of the DA string)
    if (tmPos < 0) {
      appearBuf->appendf("1 0 0 1 0 {0:.2f} Tm\n", y);
    }

    // write a series of lines of text
    i = 0;
    xPrev = 0;
    while (i < text->getLength()) {
      layoutText(text, convertedText, &i, font, &w, wMax / fontSize, NULL,
                 forceZapfDingbats);
      w *= fontSize;

      // compute text start position
      switch (quadding) {
      case quaddingLeftJustified:
      default:
        x = borderWidth + 2;
        break;
      case quaddingCentered:
        x = (dx - w) / 2;
        break;
      case quaddingRightJustified:
        x = dx - borderWidth - 2 - w;
        break;
      }

      // draw the line
      appearBuf->appendf("{0:.2f} {1:.2f} Td\n", x - xPrev, -fontSize);
      writeString(convertedText, appearBuf);
      appearBuf->append(" Tj\n");

      // next line
      xPrev = x;
    }

    // single-line text
  } else {
    //~ replace newlines with spaces? - what does Acrobat do?

    // comb formatting
    if (comb > 0) {
      int charCount;

      // compute comb spacing
      w = (dx - 2 * borderWidth) / comb;

      // compute font autosize
      if (fontSize == 0) {
        fontSize = dy - 2 * borderWidth;
        if (w < fontSize) {
          fontSize = w;
        }
        fontSize = floor(fontSize);
        if (tfPos >= 0) {
          tok = (GooString *)daToks->get(tfPos + 1);
          tok->clear();
          tok->appendf("{0:.2f}", fontSize);
        }
      }

      i = 0;
      layoutText(text, convertedText, &i, font, NULL, 0.0, &charCount,
                 forceZapfDingbats);
      if (charCount > comb)
        charCount = comb;

      // compute starting text cell
      switch (quadding) {
      case quaddingLeftJustified:
      default:
          x = borderWidth;
        break;
      case quaddingCentered:
        x = borderWidth + (comb - charCount) / 2 * w;
        break;
      case quaddingRightJustified:
        x = borderWidth + (comb - charCount) * w;
        break;
      }
      y = 0.5 * dy - 0.4 * fontSize;

      // set the font matrix
      if (tmPos >= 0) {
        tok = (GooString *)daToks->get(tmPos + 4);
        tok->clear();
        tok->appendf("{0:.2f}", x);
        tok = (GooString *)daToks->get(tmPos + 5);
        tok->clear();
        tok->appendf("{0:.2f}", y);
      }

      // write the DA string
      if (daToks) {
        for (i = 0; i < daToks->getLength(); ++i) {
          appearBuf->append((GooString *)daToks->get(i))->append(' ');
        }
      }

      // write the font matrix (if not part of the DA string)
      if (tmPos < 0) {
        appearBuf->appendf("1 0 0 1 {0:.2f} {1:.2f} Tm\n", x, y);
      }

      // write the text string
      char *s = convertedText->getCString();
      int len = convertedText->getLength();
      i = 0;
      xPrev = w;                // so that first character is placed properly
      while (i < comb && len > 0) {
        CharCode code;
        Unicode *uAux;
        int uLen, n;
        double dx, dy, ox, oy;

        dx = 0.0;
        n = font->getNextChar(s, len, &code, &uAux, &uLen, &dx, &dy, &ox, &oy);
        dx *= fontSize;

        // center each character within its cell, by advancing the text
        // position the appropriate amount relative to the start of the
        // previous character
        x = 0.5 * (w - dx);
        appearBuf->appendf("{0:.2f} 0 Td\n", x - xPrev + w);

        GooString *charBuf = new GooString(s, n);
        writeString(charBuf, appearBuf);
        appearBuf->append(" Tj\n");
        delete charBuf;

        i++;
        s += n;
        len -= n;
        xPrev = x;
      }

      // regular (non-comb) formatting
    } else {
      i = 0;
      layoutText(text, convertedText, &i, font, &w, 0.0, NULL,
                 forceZapfDingbats);

      // compute font autosize
      if (fontSize == 0) {
        fontSize = dy - 2 * borderWidth;
        fontSize2 = (dx - 4 - 2 * borderWidth) / w;
        if (fontSize2 < fontSize) {
          fontSize = fontSize2;
        }
        fontSize = floor(fontSize);
        if (tfPos >= 0) {
          tok = (GooString *)daToks->get(tfPos + 1);
          tok->clear();
          tok->appendf("{0:.2f}", fontSize);
        }
      }

      // compute text start position
      w *= fontSize;
      switch (quadding) {
      case quaddingLeftJustified:
      default:
        x = borderWidth + 2;
        break;
      case quaddingCentered:
        x = (dx - w) / 2;
        break;
      case quaddingRightJustified:
        x = dx - borderWidth - 2 - w;
        break;
      }
      y = 0.5 * dy - 0.4 * fontSize;

      // set the font matrix
      if (tmPos >= 0) {
        tok = (GooString *)daToks->get(tmPos + 4);
        tok->clear();
        tok->appendf("{0:.2f}", x);
        tok = (GooString *)daToks->get(tmPos + 5);
        tok->clear();
        tok->appendf("{0:.2f}", y);
      }

      // write the DA string
      if (daToks) {
        for (i = 0; i < daToks->getLength(); ++i) {
          appearBuf->append((GooString *)daToks->get(i))->append(' ');
        }
      }

      // write the font matrix (if not part of the DA string)
      if (tmPos < 0) {
        appearBuf->appendf("1 0 0 1 {0:.2f} {1:.2f} Tm\n", x, y);
      }

      // write the text string
      writeString(convertedText, appearBuf);
      appearBuf->append(" Tj\n");
    }
  }
  // cleanup
  appearBuf->append("ET\n");
  appearBuf->append("Q\n");
  if (txField) {
    appearBuf->append("EMC\n");
  }
  if (daToks) {
    deleteGooList(daToks, GooString);
  }
  if (freeText) {
    delete text;
  }
  delete convertedText;
  if (freeFont) {
    font->decRefCnt();
  }
}

// Draw the variable text or caption for a field.
void AnnotWidget::drawListBox(FormFieldChoice *fieldChoice,
			      GooString *da, GfxResources *resources, int quadding) {
  GooList *daToks;
  GooString *tok, *convertedText;
  GfxFont *font;
  double fontSize, fontSize2, borderWidth, x, y, w, wMax;
  int tfPos, tmPos, i, j;

  //~ if there is no MK entry, this should use the existing content stream,
  //~ and only replace the marked content portion of it
  //~ (this is only relevant for Tx fields)

  // parse the default appearance string
  tfPos = tmPos = -1;
  if (da) {
    daToks = new GooList();
    i = 0;
    while (i < da->getLength()) {
      while (i < da->getLength() && Lexer::isSpace(da->getChar(i))) {
	++i;
       }
      if (i < da->getLength()) {
	for (j = i + 1;
	     j < da->getLength() && !Lexer::isSpace(da->getChar(j));
	     ++j) ;
	daToks->append(new GooString(da, i, j - i));
	i = j;
      }
    }
    for (i = 2; i < daToks->getLength(); ++i) {
      if (i >= 2 && !((GooString *)daToks->get(i))->cmp("Tf")) {
	tfPos = i - 2;
      } else if (i >= 6 && !((GooString *)daToks->get(i))->cmp("Tm")) {
	tmPos = i - 6;
      }
    }
  } else {
    daToks = NULL;
  }

  // get the font and font size
  font = NULL;
  fontSize = 0;
  if (tfPos >= 0) {
    tok = (GooString *)daToks->get(tfPos);
    if (tok->getLength() >= 1 && tok->getChar(0) == '/') {
      if (!resources || !(font = resources->lookupFont(tok->getCString() + 1))) {
        error(errSyntaxError, -1, "Unknown font in field's DA string");
      }
    } else {
      error(errSyntaxError, -1, "Invalid font name in 'Tf' operator in field's DA string");
    }
    tok = (GooString *)daToks->get(tfPos + 1);
    fontSize = gatof(tok->getCString());
  } else {
    error(errSyntaxError, -1, "Missing 'Tf' operator in field's DA string");
  }
  if (!font) {
    if (daToks) {
      deleteGooList(daToks, GooString);
    }
    return;
  }

  convertedText = new GooString;

  // get the border width
  borderWidth = border ? border->getWidth() : 0;

  // compute font autosize
  if (fontSize == 0) {
    wMax = 0;
    for (i = 0; i < fieldChoice->getNumChoices(); ++i) {
      j = 0;
      if (fieldChoice->getChoice(i) == NULL) {
        error(errSyntaxError, -1, "Invalid annotation listbox");
        if (daToks) {
          deleteGooList(daToks, GooString);
        }
        delete convertedText;
        return;
      }
      layoutText(fieldChoice->getChoice(i), convertedText, &j, font, &w, 0.0, NULL, gFalse);
      if (w > wMax) {
        wMax = w;
      }
    }
    fontSize = rect->y2 - rect->y1 - 2 * borderWidth;
    fontSize2 = (rect->x2 - rect->x1 - 4 - 2 * borderWidth) / wMax;
    if (fontSize2 < fontSize) {
      fontSize = fontSize2;
    }
    fontSize = floor(fontSize);
    if (tfPos >= 0) {
      tok = (GooString *)daToks->get(tfPos + 1);
      tok->clear();
      tok->appendf("{0:.2f}", fontSize);
    }
  }
  // draw the text
  y = rect->y2 - rect->y1 - 1.1 * fontSize;
  for (i = fieldChoice->getTopIndex(); i < fieldChoice->getNumChoices(); ++i) {
    // setup
    appearBuf->append("q\n");

    // draw the background if selected
    if (fieldChoice->isSelected(i)) {
      appearBuf->append("0 g f\n");
      appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} re f\n",
          borderWidth,
          y - 0.2 * fontSize,
          rect->x2 - rect->x1 - 2 * borderWidth,
          1.1 * fontSize);
    }

    // setup
    appearBuf->append("BT\n");

    // compute text width and start position
    j = 0;
    layoutText(fieldChoice->getChoice(i), convertedText, &j, font, &w, 0.0, NULL, gFalse);
    w *= fontSize;
    switch (quadding) {
    case quaddingLeftJustified:
    default:
      x = borderWidth + 2;
      break;
    case quaddingCentered:
      x = (rect->x2 - rect->x1 - w) / 2;
      break;
    case quaddingRightJustified:
      x = rect->x2 - rect->x1 - borderWidth - 2 - w;
      break;
    }

    // set the font matrix
    if (tmPos >= 0) {
      tok = (GooString *)daToks->get(tmPos + 4);
      tok->clear();
      tok->appendf("{0:.2f}", x);
      tok = (GooString *)daToks->get(tmPos + 5);
      tok->clear();
      tok->appendf("{0:.2f}", y);
    }

    // write the DA string
    if (daToks) {
      for (j = 0; j < daToks->getLength(); ++j) {
        appearBuf->append((GooString *)daToks->get(j))->append(' ');
      }
    }

    // write the font matrix (if not part of the DA string)
    if (tmPos < 0) {
      appearBuf->appendf("1 0 0 1 {0:.2f} {1:.2f} Tm\n", x, y);
    }

    // change the text color if selected
    if (fieldChoice->isSelected(i)) {
      appearBuf->append("1 g\n");
    }

    // write the text string
    writeString(convertedText, appearBuf);
    appearBuf->append(" Tj\n");

    // cleanup
    appearBuf->append("ET\n");
    appearBuf->append("Q\n");

    // next line
    y -= 1.1 * fontSize;
  }

  if (daToks) {
    deleteGooList(daToks, GooString);
  }

  delete convertedText;
}

void AnnotWidget::drawBorder() {
  int dashLength;
  double *dash;
  AnnotColor adjustedColor;
  double w = border->getWidth();

  AnnotColor *aColor = appearCharacs->getBorderColor();
  if (!aColor)
    aColor = appearCharacs->getBackColor();
  if (!aColor)
    return;

  double dx = rect->x2 - rect->x1;
  double dy = rect->y2 - rect->y1;

  // radio buttons with no caption have a round border
  GBool hasCaption = appearCharacs->getNormalCaption() != NULL;
  if (field->getType() == formButton &&
      static_cast<FormFieldButton*>(field)->getButtonType() == formButtonRadio && !hasCaption) {
    double r = 0.5 * (dx < dy ? dx : dy);
    switch (border->getStyle()) {
    case AnnotBorder::borderDashed:
      appearBuf->append("[");
      dashLength = border->getDashLength();
      dash = border->getDash();
      for (int i = 0; i < dashLength; ++i) {
        appearBuf->appendf(" {0:.2f}", dash[i]);
      }
      appearBuf->append("] 0 d\n");
      // fall through to the solid case
    case AnnotBorder::borderSolid:
    case AnnotBorder::borderUnderlined:
      appearBuf->appendf("{0:.2f} w\n", w);
      setColor(aColor, gFalse);
      drawCircle(0.5 * dx, 0.5 * dy, r - 0.5 * w, gFalse);
      break;
    case AnnotBorder::borderBeveled:
    case AnnotBorder::borderInset:
      appearBuf->appendf("{0:.2f} w\n", 0.5 * w);
      setColor(aColor, gFalse);
      drawCircle(0.5 * dx, 0.5 * dy, r - 0.25 * w, gFalse);
      adjustedColor = AnnotColor(*aColor);
      adjustedColor.adjustColor(border->getStyle() == AnnotBorder::borderBeveled ? 1 : -1);
      setColor(&adjustedColor, gFalse);
      drawCircleTopLeft(0.5 * dx, 0.5 * dy, r - 0.75 * w);
      adjustedColor = AnnotColor(*aColor);
      adjustedColor.adjustColor(border->getStyle() == AnnotBorder::borderBeveled ? -1 : 1);
      setColor(&adjustedColor, gFalse);
      drawCircleBottomRight(0.5 * dx, 0.5 * dy, r - 0.75 * w);
      break;
    }
  } else {
    switch (border->getStyle()) {
    case AnnotBorder::borderDashed:
      appearBuf->append("[");
      dashLength = border->getDashLength();
      dash = border->getDash();
      for (int i = 0; i < dashLength; ++i) {
        appearBuf->appendf(" {0:.2f}", dash[i]);
      }
      appearBuf->append("] 0 d\n");
      // fall through to the solid case
    case AnnotBorder::borderSolid:
      appearBuf->appendf("{0:.2f} w\n", w);
      setColor(aColor, gFalse);
      appearBuf->appendf("{0:.2f} {0:.2f} {1:.2f} {2:.2f} re s\n",
                         0.5 * w, dx - w, dy - w);
      break;
    case AnnotBorder::borderBeveled:
    case AnnotBorder::borderInset:
      adjustedColor = AnnotColor(*aColor);
      adjustedColor.adjustColor(border->getStyle() == AnnotBorder::borderBeveled ? 1 : -1);
      setColor(&adjustedColor, gTrue);
      appearBuf->append("0 0 m\n");
      appearBuf->appendf("0 {0:.2f} l\n", dy);
      appearBuf->appendf("{0:.2f} {1:.2f} l\n", dx, dy);
      appearBuf->appendf("{0:.2f} {1:.2f} l\n", dx - w, dy - w);
      appearBuf->appendf("{0:.2f} {1:.2f} l\n", w, dy - w);
      appearBuf->appendf("{0:.2f} {0:.2f} l\n", w);
      appearBuf->append("f\n");
      adjustedColor = AnnotColor(*aColor);
      adjustedColor.adjustColor(border->getStyle() == AnnotBorder::borderBeveled ? -1 : 1);
      setColor(&adjustedColor, gTrue);
      appearBuf->append("0 0 m\n");
      appearBuf->appendf("{0:.2f} 0 l\n", dx);
      appearBuf->appendf("{0:.2f} {1:.2f} l\n", dx, dy);
      appearBuf->appendf("{0:.2f} {1:.2f} l\n", dx - w, dy - w);
      appearBuf->appendf("{0:.2f} {1:.2f} l\n", dx - w, w);
      appearBuf->appendf("{0:.2f} {0:.2f} l\n", w);
      appearBuf->append("f\n");
      break;
    case AnnotBorder::borderUnderlined:
      appearBuf->appendf("{0:.2f} w\n", w);
      setColor(aColor, gFalse);
      appearBuf->appendf("0 0 m {0:.2f} 0 l s\n", dx);
      break;
    }

    // clip to the inside of the border
    appearBuf->appendf("{0:.2f} {0:.2f} {1:.2f} {2:.2f} re W n\n",
                       w, dx - 2 * w, dy - 2 * w);
  }
}

void AnnotWidget::drawFormFieldButton(GfxResources *resources, GooString *da) {
  GooString *caption = NULL;
  if (appearCharacs)
    caption = appearCharacs->getNormalCaption();

  switch (static_cast<FormFieldButton *>(field)->getButtonType()) {
  case formButtonRadio: {
    //~ Acrobat doesn't draw a caption if there is no AP dict (?)
    if (appearState && appearState->cmp("Off") != 0 &&
        static_cast<FormFieldButton *>(field)->getState(appearState->getCString())) {
      if (caption) {
        drawText(caption, da, resources, gFalse, 0, fieldQuadCenter,
                 gFalse, gTrue);
      } else if (appearCharacs) {
        AnnotColor *aColor = appearCharacs->getBorderColor();
        if (aColor) {
          double dx = rect->x2 - rect->x1;
          double dy = rect->y2 - rect->y1;
          setColor(aColor, gTrue);
          drawCircle(0.5 * dx, 0.5 * dy, 0.2 * (dx < dy ? dx : dy), gTrue);
        }
      }
    }
  }
    break;
  case formButtonPush:
    if (caption)
      drawText(caption, da, resources, gFalse, 0, fieldQuadCenter, gFalse, gFalse);
    break;
  case formButtonCheck:
    if (appearState && appearState->cmp("Off") != 0) {
      if (!caption) {
        GooString checkMark("3");
        drawText(&checkMark, da, resources, gFalse, 0, fieldQuadCenter, gFalse, gTrue);
      } else {
        drawText(caption, da, resources, gFalse, 0, fieldQuadCenter, gFalse, gTrue);
      }
    }
    break;
  }
}

void AnnotWidget::drawFormFieldText(GfxResources *resources, GooString *da) {
  VariableTextQuadding quadding;
  GooString *contents;
  FormFieldText *fieldText = static_cast<FormFieldText *>(field);

  contents = fieldText->getContent();
  if (contents) {
    quadding = field->hasTextQuadding() ? field->getTextQuadding() : form->getTextQuadding();

    int comb = 0;
    if (fieldText->isComb())
      comb = fieldText->getMaxLen();

    drawText(contents, da, resources,
             fieldText->isMultiline(), comb, quadding, gTrue, gFalse, fieldText->isPassword());
  }
}

void AnnotWidget::drawFormFieldChoice(GfxResources *resources, GooString *da) {
  GooString *selected;
  VariableTextQuadding quadding;
  FormFieldChoice *fieldChoice = static_cast<FormFieldChoice *>(field);

  quadding = field->hasTextQuadding() ? field->getTextQuadding() : form->getTextQuadding();

  if (fieldChoice->isCombo()) {
    selected = fieldChoice->getSelectedChoice();
    if (selected) {
      drawText(selected, da, resources, gFalse, 0, quadding, gTrue, gFalse);
      //~ Acrobat draws a popup icon on the right side
    }
  // list box
  } else {
    drawListBox(fieldChoice, da, resources, quadding);
  }
}

void AnnotWidget::generateFieldAppearance() {
  Object appearDict, obj1, obj2;
  GfxResources *resources;
  MemStream *appearStream;
  GooString *da;

  appearBuf = new GooString ();

  // draw the background
  if (appearCharacs) {
    AnnotColor *aColor = appearCharacs->getBackColor();
    if (aColor) {
      setColor(aColor, gTrue);
      appearBuf->appendf("0 0 {0:.2f} {1:.2f} re f\n",
                         rect->x2 - rect->x1, rect->y2 - rect->y1);
    }
  }

  // draw the border
  if (appearCharacs && border && border->getWidth() > 0)
    drawBorder();

  da = field->getDefaultAppearance();
  if (!da)
    da = form->getDefaultAppearance();

  resources = form->getDefaultResources();

  // draw the field contents
  switch (field->getType()) {
  case formButton:
    drawFormFieldButton(resources, da);
    break;
  case formText:
    drawFormFieldText(resources, da);
    break;
  case formChoice:
    drawFormFieldChoice(resources, da);
    break;
  case formSignature:
    //~unimp
    break;
  case formUndef:
  default:
    error(errSyntaxError, -1, "Unknown field type");
  }

  // build the appearance stream dictionary
  appearDict.initDict(xref);
  appearDict.dictAdd(copyString("Length"),
      obj1.initInt(appearBuf->getLength()));
  appearDict.dictAdd(copyString("Subtype"), obj1.initName("Form"));
  obj1.initArray(xref);
  obj1.arrayAdd(obj2.initReal(0));
  obj1.arrayAdd(obj2.initReal(0));
  obj1.arrayAdd(obj2.initReal(rect->x2 - rect->x1));
  obj1.arrayAdd(obj2.initReal(rect->y2 - rect->y1));
  appearDict.dictAdd(copyString("BBox"), &obj1);

  // set the resource dictionary
  Object *resDict = form->getDefaultResourcesObj();
  if (resDict->isDict()) {
    appearDict.dictAdd(copyString("Resources"), resDict->copy(&obj1));
  }

  // build the appearance stream
  appearStream = new MemStream(copyString(appearBuf->getCString()), 0,
      appearBuf->getLength(), &appearDict);
  appearance.free();
  appearance.initStream(appearStream);
  delete appearBuf;

  appearStream->setNeedFree(gTrue);
}

void AnnotWidget::updateAppearanceStream()
{
  // If this the first time updateAppearanceStream() is called on this widget,
  // destroy the AP dictionary because we are going to create a new one.
  if (updatedAppearanceStream.num == -1) {
    invalidateAppearance(); // Delete AP dictionary and all referenced streams
  }

  // There's no need to create a new appearance stream if NeedAppearances is
  // set, because it will be ignored next time anyway.
  if (form && form->getNeedAppearances())
    return;

  // Create the new appearance
  generateFieldAppearance();

  // Fetch the appearance stream we've just created
  Object obj1;
  appearance.fetch(xref, &obj1);

  // If this the first time updateAppearanceStream() is called on this widget,
  // create a new AP dictionary containing the new appearance stream.
  // Otherwise, just update the stream we had created previously.
  if (updatedAppearanceStream.num == -1) {
    // Write the appearance stream
    updatedAppearanceStream = xref->addIndirectObject(&obj1);
    obj1.free();

    // Write the AP dictionary
    Object obj2;
    obj1.initDict(xref);
    obj1.dictAdd(copyString("N"), obj2.initRef(updatedAppearanceStream.num, updatedAppearanceStream.gen));
    update("AP", &obj1);

    // Update our internal pointers to the appearance dictionary
    appearStreams = new AnnotAppearance(doc, &obj1);
  } else {
    // Replace the existing appearance stream
    xref->setModifiedObject(&obj1, updatedAppearanceStream);
    obj1.free();
  }
}

void AnnotWidget::draw(Gfx *gfx, GBool printing) {
  Object obj;

  if (!isVisible (printing))
    return;

  annotLocker();
  addDingbatsResource = gFalse;

  // Only construct the appearance stream when
  // - annot doesn't have an AP or
  // - NeedAppearances is true
  if (field) {
    if (appearance.isNull() || (form && form->getNeedAppearances()))
      generateFieldAppearance();
  }

  // draw the appearance stream
  appearance.fetch(gfx->getXRef(), &obj);
  if (addDingbatsResource) {
    // We are forcing ZaDb but the font does not exist
    // so create a fake one
    Object baseFontObj, subtypeObj;
    baseFontObj.initName("ZapfDingbats");
    subtypeObj.initName("Type1");

    Object fontDictObj;
    Dict *fontDict = new Dict(gfx->getXRef());
    fontDict->decRef();
    fontDict->add(copyString("BaseFont"), &baseFontObj);
    fontDict->add(copyString("Subtype"), &subtypeObj);
    fontDictObj.initDict(fontDict);

    Object fontsDictObj;
    Dict *fontsDict = new Dict(gfx->getXRef());
    fontsDict->decRef();
    fontsDict->add(copyString("ZaDb"), &fontDictObj);
    fontsDictObj.initDict(fontsDict);

    Dict *dict = new Dict(gfx->getXRef());
    dict->add(copyString("Font"), &fontsDictObj);
    gfx->pushResources(dict);
    delete dict;
  }
  gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color,
		 rect->x1, rect->y1, rect->x2, rect->y2, getRotation());
  if (addDingbatsResource) {
    gfx->popResources();
  }
  obj.free();
}


//------------------------------------------------------------------------
// AnnotMovie
//------------------------------------------------------------------------
AnnotMovie::AnnotMovie(PDFDoc *docA, PDFRectangle *rect, Movie *movieA) :
    Annot(docA, rect) {
  Object obj1;

  type = typeMovie;
  annotObj.dictSet ("Subtype", obj1.initName ("Movie"));

  movie = movieA->copy();
  // TODO: create movie dict from movieA

  initialize(docA, annotObj.getDict());
}

AnnotMovie::AnnotMovie(PDFDoc *docA, Dict *dict, Object *obj) :
  Annot(docA, dict, obj) {
  type = typeMovie;
  initialize(docA, dict);
}

AnnotMovie::~AnnotMovie() {
  if (title)
    delete title;
  delete movie;
}

void AnnotMovie::initialize(PDFDoc *docA, Dict* dict) {
  Object obj1;

  if (dict->lookup("T", &obj1)->isString()) {
    title = obj1.getString()->copy();
  } else {
    title = NULL;
  }
  obj1.free();

  Object movieDict;
  if (dict->lookup("Movie", &movieDict)->isDict()) {
    Object obj2;
    dict->lookup("A", &obj2);
    if (obj2.isDict())
      movie = new Movie (&movieDict, &obj2);
    else
      movie = new Movie (&movieDict);
    if (!movie->isOk()) {
      delete movie;
      movie = NULL;
      ok = gFalse;
    }
    obj2.free();
  } else {
    error(errSyntaxError, -1, "Bad Annot Movie");
    movie = NULL;
    ok = gFalse;
  }
  movieDict.free();
}

void AnnotMovie::draw(Gfx *gfx, GBool printing) {
  Object obj;

  if (!isVisible (printing))
    return;

  annotLocker();
  if (appearance.isNull() && movie->getShowPoster()) {
    int width, height;
    Object poster;
    movie->getPoster(&poster);
    movie->getAspect(&width, &height);

    if (width != -1 && height != -1 && !poster.isNone()) {
      MemStream *mStream;

      appearBuf = new GooString ();
      appearBuf->append ("q\n");
      appearBuf->appendf ("{0:d} 0 0 {1:d} 0 0 cm\n", width, height);
      appearBuf->append ("/MImg Do\n");
      appearBuf->append ("Q\n");

      Object imgDict;
      imgDict.initDict(gfx->getXRef());
      imgDict.dictSet ("MImg", &poster);

      Object resDict;
      resDict.initDict(gfx->getXRef());
      resDict.dictSet ("XObject", &imgDict);

      Object formDict, obj1, obj2;
      formDict.initDict(gfx->getXRef());
      formDict.dictSet("Length", obj1.initInt(appearBuf->getLength()));
      formDict.dictSet("Subtype", obj1.initName("Form"));
      formDict.dictSet("Name", obj1.initName("FRM"));
      obj1.initArray(gfx->getXRef());
      obj1.arrayAdd(obj2.initInt(0));
      obj1.arrayAdd(obj2.initInt(0));
      obj1.arrayAdd(obj2.initInt(width));
      obj1.arrayAdd(obj2.initInt(height));
      formDict.dictSet("BBox", &obj1);
      obj1.initArray(gfx->getXRef());
      obj1.arrayAdd(obj2.initInt(1));
      obj1.arrayAdd(obj2.initInt(0));
      obj1.arrayAdd(obj2.initInt(0));
      obj1.arrayAdd(obj2.initInt(1));
      obj1.arrayAdd(obj2.initInt(-width / 2));
      obj1.arrayAdd(obj2.initInt(-height / 2));
      formDict.dictSet("Matrix", &obj1);
      formDict.dictSet("Resources", &resDict);

      Object aStream;
      mStream = new MemStream(copyString(appearBuf->getCString()), 0,
			      appearBuf->getLength(), &formDict);
      mStream->setNeedFree(gTrue);
      aStream.initStream(mStream);
      delete appearBuf;

      Object objDict;
      objDict.initDict(gfx->getXRef());
      objDict.dictSet ("FRM", &aStream);

      resDict.initDict(gfx->getXRef());
      resDict.dictSet ("XObject", &objDict);

      appearBuf = new GooString ();
      appearBuf->append ("q\n");
      appearBuf->appendf ("0 0 {0:d} {1:d} re W n\n", width, height);
      appearBuf->append ("q\n");
      appearBuf->appendf ("0 0 {0:d} {1:d} re W n\n", width, height);
      appearBuf->appendf ("1 0 0 1 {0:d} {1:d} cm\n", width / 2, height / 2);
      appearBuf->append ("/FRM Do\n");
      appearBuf->append ("Q\n");
      appearBuf->append ("Q\n");

      double bbox[4];
      bbox[0] = bbox[1] = 0;
      bbox[2] = width;
      bbox[3] = height;
      createForm(bbox, gFalse, &resDict, &appearance);
      delete appearBuf;
    }
    poster.free();
  }

  // draw the appearance stream
  appearance.fetch(gfx->getXRef(), &obj);
  gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color,
		 rect->x1, rect->y1, rect->x2, rect->y2, getRotation());
  obj.free();
}

//------------------------------------------------------------------------
// AnnotScreen
//------------------------------------------------------------------------
AnnotScreen::AnnotScreen(PDFDoc *docA, PDFRectangle *rect) :
    Annot(docA, rect) {
  Object obj1;

  type = typeScreen;

  annotObj.dictSet ("Subtype", obj1.initName ("Screen"));
  initialize(docA, annotObj.getDict());
}

AnnotScreen::AnnotScreen(PDFDoc *docA, Dict *dict, Object *obj) :
  Annot(docA, dict, obj) {
  type = typeScreen;
  initialize(docA, dict);
}

AnnotScreen::~AnnotScreen() {
  if (title)
    delete title;
  if (appearCharacs)
    delete appearCharacs;
  if (action)
    delete action;

  additionalActions.free();
}

void AnnotScreen::initialize(PDFDoc *docA, Dict* dict) {
  Object obj1;

  title = NULL;
  if (dict->lookup("T", &obj1)->isString()) {
    title = obj1.getString()->copy();
  }
  obj1.free();

  action = NULL;
  if (dict->lookup("A", &obj1)->isDict()) {
    action = LinkAction::parseAction(&obj1, doc->getCatalog()->getBaseURI());
    if (action && action->getKind() == actionRendition && page == 0) {
      error (errSyntaxError, -1, "Invalid Rendition action: associated screen annotation without P");
      delete action;
      action = NULL;
      ok = gFalse;
    }
  }
  obj1.free();

  dict->lookupNF("AA", &additionalActions);

  appearCharacs = NULL;
  if(dict->lookup("MK", &obj1)->isDict()) {
    appearCharacs = new AnnotAppearanceCharacs(obj1.getDict());
  }
  obj1.free();
}

LinkAction* AnnotScreen::getAdditionalAction(AdditionalActionsType type)
{
  if (type == actionFocusIn || type == actionFocusOut) // not defined for screen annotation
    return NULL;

  return ::getAdditionalAction(type, &additionalActions, doc);
}

//------------------------------------------------------------------------
// AnnotStamp
//------------------------------------------------------------------------
AnnotStamp::AnnotStamp(PDFDoc *docA, PDFRectangle *rect) :
  AnnotMarkup(docA, rect) {
  Object obj1;

  type = typeStamp;
  annotObj.dictSet ("Subtype", obj1.initName ("Stamp"));
  initialize(docA, annotObj.getDict());
}

AnnotStamp::AnnotStamp(PDFDoc *docA, Dict *dict, Object *obj) :
  AnnotMarkup(docA, dict, obj) {
  type = typeStamp;
  initialize(docA, dict);
}

AnnotStamp::~AnnotStamp() {
  delete icon;
}

void AnnotStamp::initialize(PDFDoc *docA, Dict* dict) {
  Object obj1;

  if (dict->lookup("Name", &obj1)->isName()) {
    icon = new GooString(obj1.getName());
  } else {
    icon = new GooString("Draft");
  }
  obj1.free();

}

void AnnotStamp::setIcon(GooString *new_icon) {
  delete icon;

  if (new_icon) {
    icon = new GooString (new_icon);
  } else {
    icon = new GooString();
  }

  Object obj1;
  obj1.initName (icon->getCString());
  update("Name", &obj1);
  invalidateAppearance();
}

//------------------------------------------------------------------------
// AnnotGeometry
//------------------------------------------------------------------------
AnnotGeometry::AnnotGeometry(PDFDoc *docA, PDFRectangle *rect, AnnotSubtype subType) :
    AnnotMarkup(docA, rect) {
  Object obj1;

  switch (subType) {
    case typeSquare:
      annotObj.dictSet ("Subtype", obj1.initName ("Square"));
      break;
    case typeCircle:
      annotObj.dictSet ("Subtype", obj1.initName ("Circle"));
      break;
    default:
      assert (0 && "Invalid subtype for AnnotGeometry\n");
  }

  initialize(docA, annotObj.getDict());
}

AnnotGeometry::AnnotGeometry(PDFDoc *docA, Dict *dict, Object *obj) :
  AnnotMarkup(docA, dict, obj) {
  // the real type will be read in initialize()
  type = typeSquare;
  initialize(docA, dict);
}

AnnotGeometry::~AnnotGeometry() {
  delete interiorColor;
  delete borderEffect;
  delete geometryRect;
}

void AnnotGeometry::initialize(PDFDoc *docA, Dict* dict) {
  Object obj1;

  if (dict->lookup("Subtype", &obj1)->isName()) {
    GooString typeName(obj1.getName());
    if (!typeName.cmp("Square")) {
      type = typeSquare;
    } else if (!typeName.cmp("Circle")) {
      type = typeCircle;
    }
  }
  obj1.free();

  if (dict->lookup("IC", &obj1)->isArray()) {
    interiorColor = new AnnotColor(obj1.getArray());
  } else {
    interiorColor = NULL;
  }
  obj1.free();

  if (dict->lookup("BS", &obj1)->isDict()) {
    delete border;
    border = new AnnotBorderBS(obj1.getDict());
  } else if (!border) {
    border = new AnnotBorderBS();
  }
  obj1.free();

  if (dict->lookup("BE", &obj1)->isDict()) {
    borderEffect = new AnnotBorderEffect(obj1.getDict());
  } else {
    borderEffect = NULL;
  }
  obj1.free();

  geometryRect = NULL;
  if (dict->lookup("RD", &obj1)->isArray()) {
    geometryRect = parseDiffRectangle(obj1.getArray(), rect);
  }
  obj1.free();

}

void AnnotGeometry::setType(AnnotSubtype new_type) {
  Object obj1;

  switch (new_type) {
    case typeSquare:
      obj1.initName("Square");
      break;
    case typeCircle:
      obj1.initName("Circle");
      break;
    default:
      assert(!"Invalid subtype");
  }

  type = new_type;
  update("Subtype", &obj1);
  invalidateAppearance();
}

void AnnotGeometry::setInteriorColor(AnnotColor *new_color) {
  delete interiorColor;

  if (new_color) {
    Object obj1;
    new_color->writeToObject(xref, &obj1);
    update ("IC", &obj1);
    interiorColor = new_color;
  } else {
    interiorColor = NULL;
  }
  invalidateAppearance();
}

void AnnotGeometry::draw(Gfx *gfx, GBool printing) {
  Object obj;
  double ca = 1;

  if (!isVisible (printing))
    return;

  annotLocker();
  if (appearance.isNull()) {
    ca = opacity;

    appearBuf = new GooString ();
    appearBuf->append ("q\n");
    if (color)
      setColor(color, gFalse);

    double borderWidth = border->getWidth();
    setLineStyleForBorder(border);

    if (interiorColor)
      setColor(interiorColor, gTrue);

    if (type == typeSquare) {
      appearBuf->appendf ("{0:.2f} {1:.2f} {2:.2f} {3:.2f} re\n",
                          borderWidth / 2.0, borderWidth / 2.0,
                          (rect->x2 - rect->x1) - borderWidth,
                          (rect->y2 - rect->y1) - borderWidth);
    } else {
      double width, height;
      double b;
      double x1, y1, x2, y2, x3, y3;

      width = rect->x2 - rect->x1;
      height = rect->y2 - rect->y1;
      b = borderWidth / 2.0;

      x1 = b;
      y1 = height / 2.0;
      appearBuf->appendf ("{0:.2f} {1:.2f} m\n", x1, y1);

      y1 += height / 4.0;
      x2 = width / 4.0;
      y2 = height - b;
      x3 = width / 2.0;
      y3 = y2;
      appearBuf->appendf ("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
                          x1, y1, x2, y2, x3, y3);
      x2 = width - b;
      y2 = y1;
      x1 = x3 + (width / 4.0);
      y1 = y3;
      x3 = x2;
      y3 = height / 2.0;
      appearBuf->appendf ("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
                          x1, y1, x2, y2, x3, y3);

      x2 = x1;
      y2 = b;
      x1 = x3;
      y1 = height / 4.0;
      x3 = width / 2.0;
      y3 = b;
      appearBuf->appendf ("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
                          x1, y1, x2, y2, x3, y3);

      x2 = b;
      y2 = y1;
      x1 = width / 4.0;
      y1 = b;
      x3 = b;
      y3 = height / 2.0;
      appearBuf->appendf ("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
                          x1, y1, x2, y2, x3, y3);
    }

    if (interiorColor && interiorColor->getSpace() != AnnotColor::colorTransparent)
      appearBuf->append ("b\n");
    else
      appearBuf->append ("S\n");

    appearBuf->append ("Q\n");

    double bbox[4];
    bbox[0] = bbox[1] = 0;
    bbox[2] = rect->x2 - rect->x1;
    bbox[3] = rect->y2 - rect->y1;
    if (ca == 1) {
      createForm(bbox, gFalse, NULL, &appearance);
    } else {
      Object aStream;

      createForm(bbox, gTrue, NULL, &aStream);
      delete appearBuf;

      Object resDict;
      appearBuf = new GooString ("/GS0 gs\n/Fm0 Do");
      createResourcesDict("Fm0", &aStream, "GS0", ca, NULL, &resDict);
      createForm(bbox, gFalse, &resDict, &appearance);
    }
    delete appearBuf;
  }

  // draw the appearance stream
  appearance.fetch(gfx->getXRef(), &obj);
  gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color,
		 rect->x1, rect->y1, rect->x2, rect->y2, getRotation());
  obj.free();
}

//------------------------------------------------------------------------
// AnnotPolygon
//------------------------------------------------------------------------
AnnotPolygon::AnnotPolygon(PDFDoc *docA, PDFRectangle *rect, AnnotSubtype subType) :
    AnnotMarkup(docA, rect) {
  Object obj1;

  switch (subType) {
    case typePolygon:
      annotObj.dictSet ("Subtype", obj1.initName ("Polygon"));
      break;
    case typePolyLine:
      annotObj.dictSet ("Subtype", obj1.initName ("PolyLine"));
      break;
    default:
      assert (0 && "Invalid subtype for AnnotGeometry\n");
  }

  // Store dummy path with one null vertex only
  Object obj2, obj3;
  obj2.initArray (doc->getXRef());
  obj2.arrayAdd (obj3.initReal (0));
  obj2.arrayAdd (obj3.initReal (0));
  annotObj.dictSet ("Vertices", &obj2);

  initialize(docA, annotObj.getDict());
}

AnnotPolygon::AnnotPolygon(PDFDoc *docA, Dict *dict, Object *obj) :
  AnnotMarkup(docA, dict, obj) {
  // the real type will be read in initialize()
  type = typePolygon;
  initialize(docA, dict);
}

AnnotPolygon::~AnnotPolygon() {
  delete vertices;

  if (interiorColor)
    delete interiorColor;

  if (borderEffect)
    delete borderEffect;
}

void AnnotPolygon::initialize(PDFDoc *docA, Dict* dict) {
  Object obj1;

  if (dict->lookup("Subtype", &obj1)->isName()) {
    GooString typeName(obj1.getName());
    if (!typeName.cmp("Polygon")) {
      type = typePolygon;
    } else if (!typeName.cmp("PolyLine")) {
      type = typePolyLine;
    }
  }
  obj1.free();

  if (dict->lookup("Vertices", &obj1)->isArray()) {
    vertices = new AnnotPath(obj1.getArray());
  } else {
    vertices = new AnnotPath();
    error(errSyntaxError, -1, "Bad Annot Polygon Vertices");
    ok = gFalse;
  }
  obj1.free();

  if (dict->lookup("LE", &obj1)->isArray() && obj1.arrayGetLength() == 2) {
    Object obj2;

    if(obj1.arrayGet(0, &obj2)->isString())
      startStyle = parseAnnotLineEndingStyle(obj2.getString());
    else
      startStyle = annotLineEndingNone;
    obj2.free();

    if(obj1.arrayGet(1, &obj2)->isString())
      endStyle = parseAnnotLineEndingStyle(obj2.getString());
    else
      endStyle = annotLineEndingNone;
    obj2.free();

  } else {
    startStyle = endStyle = annotLineEndingNone;
  }
  obj1.free();

  if (dict->lookup("IC", &obj1)->isArray()) {
    interiorColor = new AnnotColor(obj1.getArray());
  } else {
    interiorColor = NULL;
  }
  obj1.free();

  if (dict->lookup("BS", &obj1)->isDict()) {
    delete border;
    border = new AnnotBorderBS(obj1.getDict());
  } else if (!border) {
    border = new AnnotBorderBS();
  }
  obj1.free();

  if (dict->lookup("BE", &obj1)->isDict()) {
    borderEffect = new AnnotBorderEffect(obj1.getDict());
  } else {
    borderEffect = NULL;
  }
  obj1.free();

  if (dict->lookup("IT", &obj1)->isName()) {
    const char *intentName = obj1.getName();

    if(!strcmp(intentName, "PolygonCloud")) {
      intent = polygonCloud;
    } else if(!strcmp(intentName, "PolyLineDimension")) {
      intent = polylineDimension;
    } else {
      intent = polygonDimension;
    }
  } else {
    intent = polygonCloud;
  }
  obj1.free();
}

void AnnotPolygon::setType(AnnotSubtype new_type) {
  Object obj1;

  switch (new_type) {
    case typePolygon:
      obj1.initName("Polygon");
      break;
    case typePolyLine:
      obj1.initName("PolyLine");
      break;
    default:
      assert(!"Invalid subtype");
  }

  type = new_type;
  update("Subtype", &obj1);
  invalidateAppearance();
}

void AnnotPolygon::setVertices(AnnotPath *path) {
  Object obj1, obj2;
  delete vertices;

  obj1.initArray(xref);

  for (int i = 0; i < path->getCoordsLength(); i++) {
    obj1.arrayAdd (obj2.initReal (path->getX(i)));
    obj1.arrayAdd (obj2.initReal (path->getY(i)));
  }

  vertices = new AnnotPath(obj1.getArray());

  update("Vertices", &obj1);
  invalidateAppearance();
}

void AnnotPolygon::setStartEndStyle(AnnotLineEndingStyle start, AnnotLineEndingStyle end) {
  Object obj1, obj2;

  startStyle = start;
  endStyle = end;

  obj1.initArray(xref);
  obj1.arrayAdd( obj2.initName(convertAnnotLineEndingStyle( startStyle )) );
  obj1.arrayAdd( obj2.initName(convertAnnotLineEndingStyle( endStyle )) );

  update("LE", &obj1);
  invalidateAppearance();
}

void AnnotPolygon::setInteriorColor(AnnotColor *new_color) {
  delete interiorColor;

  if (new_color) {
    Object obj1;
    new_color->writeToObject(xref, &obj1);
    update ("IC", &obj1);
    interiorColor = new_color;
  } else {
    interiorColor = NULL;
  }
  invalidateAppearance();
}

void AnnotPolygon::setIntent(AnnotPolygonIntent new_intent) {
  Object obj1;

  intent = new_intent;
  if (new_intent == polygonCloud)
    obj1.initName("PolygonCloud");
  else if (new_intent == polylineDimension)
    obj1.initName("PolyLineDimension");
  else // polygonDimension
    obj1.initName("PolygonDimension");
  update ("IT", &obj1);
}

void AnnotPolygon::draw(Gfx *gfx, GBool printing) {
  Object obj;
  double ca = 1;

  if (!isVisible (printing))
    return;

  annotLocker();
  if (appearance.isNull()) {
    appearBBox = new AnnotAppearanceBBox(rect);
    ca = opacity;

    appearBuf = new GooString ();
    appearBuf->append ("q\n");

    if (color) {
      setColor(color, gFalse);
    }

    setLineStyleForBorder(border);
    appearBBox->setBorderWidth(std::max(1., border->getWidth()));

    if (interiorColor) {
      setColor(interiorColor, gTrue);
    }

    if (vertices->getCoordsLength() != 0) {
      appearBuf->appendf ("{0:.2f} {1:.2f} m\n", vertices->getX(0) - rect->x1, vertices->getY(0) - rect->y1);
      appearBBox->extendTo (vertices->getX(0) - rect->x1, vertices->getY(0) - rect->y1);

      for (int i = 1; i < vertices->getCoordsLength(); ++i) {
        appearBuf->appendf ("{0:.2f} {1:.2f} l\n", vertices->getX(i) - rect->x1, vertices->getY(i) - rect->y1);
        appearBBox->extendTo (vertices->getX(i) - rect->x1, vertices->getY(i) - rect->y1);
      }

      if (type == typePolygon) {
        if (interiorColor && interiorColor->getSpace() != AnnotColor::colorTransparent) {
          appearBuf->append ("b\n");
        } else {
          appearBuf->append ("s\n");
        }
      } else {
        appearBuf->append ("S\n");
      }
    }

    appearBuf->append ("Q\n");

    double bbox[4];
    appearBBox->getBBoxRect(bbox);
    if (ca == 1) {
      createForm(bbox, gFalse, NULL, &appearance);
    } else {
      Object aStream, resDict;

      createForm(bbox, gTrue, NULL, &aStream);
      delete appearBuf;

      appearBuf = new GooString ("/GS0 gs\n/Fm0 Do");
      createResourcesDict("Fm0", &aStream, "GS0", ca, NULL, &resDict);
      createForm(bbox, gFalse, &resDict, &appearance);
    }
    delete appearBuf;
  }

  // draw the appearance stream
  appearance.fetch(gfx->getXRef(), &obj);
  if (appearBBox) {
    gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color,
                   appearBBox->getPageXMin(), appearBBox->getPageYMin(),
                   appearBBox->getPageXMax(), appearBBox->getPageYMax(),
                   getRotation());
  } else {
    gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color,
                   rect->x1, rect->y1, rect->x2, rect->y2, getRotation());
  }
  obj.free();
}

//------------------------------------------------------------------------
// AnnotCaret
//------------------------------------------------------------------------
AnnotCaret::AnnotCaret(PDFDoc *docA, PDFRectangle *rect) :
    AnnotMarkup(docA, rect) {
  Object obj1;

  type = typeCaret;

  annotObj.dictSet ("Subtype", obj1.initName ("Caret"));
  initialize(docA, annotObj.getDict());
}

AnnotCaret::AnnotCaret(PDFDoc *docA, Dict *dict, Object *obj) :
  AnnotMarkup(docA, dict, obj) {
  type = typeCaret;
  initialize(docA, dict);
}

AnnotCaret::~AnnotCaret() {
  delete caretRect;
}

void AnnotCaret::initialize(PDFDoc *docA, Dict* dict) {
  Object obj1;

  symbol = symbolNone;
  if (dict->lookup("Sy", &obj1)->isName()) {
    GooString typeName(obj1.getName());
    if (!typeName.cmp("P")) {
      symbol = symbolP;
    } else if (!typeName.cmp("None")) {
      symbol = symbolNone;
    }
  }
  obj1.free();

  if (dict->lookup("RD", &obj1)->isArray()) {
    caretRect = parseDiffRectangle(obj1.getArray(), rect);
  } else {
    caretRect = NULL;
  }
  obj1.free();

}

void AnnotCaret::setSymbol(AnnotCaretSymbol new_symbol) {
  Object obj1;
  obj1.initName( new_symbol == symbolP ? "P" : "None" );
  symbol = new_symbol;
  update("Sy", &obj1);
  invalidateAppearance();
}

//------------------------------------------------------------------------
// AnnotInk
//------------------------------------------------------------------------
AnnotInk::AnnotInk(PDFDoc *docA, PDFRectangle *rect) :
    AnnotMarkup(docA, rect) {
  Object obj1;

  type = typeInk;

  annotObj.dictSet ("Subtype", obj1.initName ("Ink"));

  // Store dummy path with one null vertex only
  Object obj2, obj3, obj4;
  obj2.initArray (doc->getXRef());
  obj2.arrayAdd (obj3.initArray (doc->getXRef()));
  obj3.arrayAdd (obj4.initReal (0));
  obj3.arrayAdd (obj4.initReal (0));
  annotObj.dictSet ("InkList", &obj2);

  initialize(docA, annotObj.getDict());
}

AnnotInk::AnnotInk(PDFDoc *docA, Dict *dict, Object *obj) :
  AnnotMarkup(docA, dict, obj) {
  type = typeInk;
  initialize(docA, dict);
}

AnnotInk::~AnnotInk() {
  freeInkList();
}

void AnnotInk::initialize(PDFDoc *docA, Dict* dict) {
  Object obj1;

  if (dict->lookup("InkList", &obj1)->isArray()) {
    parseInkList(obj1.getArray());
  } else {
    inkListLength = 0;
    inkList = NULL;
    error(errSyntaxError, -1, "Bad Annot Ink List");
    ok = gFalse;
  }
  obj1.free();

  if (dict->lookup("BS", &obj1)->isDict()) {
    delete border;
    border = new AnnotBorderBS(obj1.getDict());
  } else if (!border) {
    border = new AnnotBorderBS();
  }
  obj1.free();
}

void AnnotInk::writeInkList(AnnotPath **paths, int n_paths, Array *dest_array) {
  Object obj1, obj2;
  for (int i = 0; i < n_paths; ++i) {
    AnnotPath *path = paths[i];
    obj1.initArray (xref);
    for (int j = 0; j < path->getCoordsLength(); ++j) {
      obj1.arrayAdd (obj2.initReal (path->getX(j)));
      obj1.arrayAdd (obj2.initReal (path->getY(j)));
    }
    dest_array->add (&obj1);
  }
}

void AnnotInk::parseInkList(Array *array) {
  inkListLength = array->getLength();
  inkList = (AnnotPath **) gmallocn ((inkListLength), sizeof(AnnotPath *));
  memset(inkList, 0, inkListLength * sizeof(AnnotPath *));
  for (int i = 0; i < inkListLength; i++) {
    Object obj2;
    if (array->get(i, &obj2)->isArray())
      inkList[i] = new AnnotPath(obj2.getArray());
    obj2.free();
  }
}

void AnnotInk::freeInkList() {
  if (inkList) {
    for (int i = 0; i < inkListLength; ++i)
      delete inkList[i];
    gfree(inkList);
  }
}

void AnnotInk::setInkList(AnnotPath **paths, int n_paths) {
  Object obj1;

  freeInkList();

  obj1.initArray (xref);
  writeInkList(paths, n_paths, obj1.getArray());

  parseInkList(obj1.getArray());
  annotObj.dictSet ("InkList", &obj1);
  invalidateAppearance();
}

void AnnotInk::draw(Gfx *gfx, GBool printing) {
  Object obj;
  double ca = 1;

  if (!isVisible (printing))
    return;

  annotLocker();
  if (appearance.isNull()) {
    appearBBox = new AnnotAppearanceBBox(rect);
    ca = opacity;

    appearBuf = new GooString ();
    appearBuf->append ("q\n");

    if (color) {
      setColor(color, gFalse);
    }

    setLineStyleForBorder(border);
    appearBBox->setBorderWidth(std::max(1., border->getWidth()));

    for (int i = 0; i < inkListLength; ++i) {
      const AnnotPath * path = inkList[i];
      if (path && path->getCoordsLength() != 0) {
        appearBuf->appendf ("{0:.2f} {1:.2f} m\n", path->getX(0) - rect->x1, path->getY(0) - rect->y1);
        appearBBox->extendTo (path->getX(0) - rect->x1, path->getY(0) - rect->y1);

        for (int j = 1; j < path->getCoordsLength(); ++j) {
          appearBuf->appendf ("{0:.2f} {1:.2f} l\n", path->getX(j) - rect->x1, path->getY(j) - rect->y1);
          appearBBox->extendTo (path->getX(j) - rect->x1, path->getY(j) - rect->y1);
        }

        appearBuf->append ("S\n");
      }
    }

    appearBuf->append ("Q\n");

    double bbox[4];
    appearBBox->getBBoxRect(bbox);
    if (ca == 1) {
      createForm(bbox, gFalse, NULL, &appearance);
    } else {
      Object aStream, resDict;

      createForm(bbox, gTrue, NULL, &aStream);
      delete appearBuf;

      appearBuf = new GooString ("/GS0 gs\n/Fm0 Do");
      createResourcesDict("Fm0", &aStream, "GS0", ca, NULL, &resDict);
      createForm(bbox, gFalse, &resDict, &appearance);
    }
    delete appearBuf;
  }

  // draw the appearance stream
  appearance.fetch(gfx->getXRef(), &obj);
  if (appearBBox) {
    gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color,
                   appearBBox->getPageXMin(), appearBBox->getPageYMin(),
                   appearBBox->getPageXMax(), appearBBox->getPageYMax(),
                   getRotation());
  } else {
    gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color,
                   rect->x1, rect->y1, rect->x2, rect->y2, getRotation());
  }
  obj.free();
}

//------------------------------------------------------------------------
// AnnotFileAttachment
//------------------------------------------------------------------------
AnnotFileAttachment::AnnotFileAttachment(PDFDoc *docA, PDFRectangle *rect, GooString *filename) :
    AnnotMarkup(docA, rect) {
  Object obj1;

  type = typeFileAttachment;

  annotObj.dictSet ("Subtype", obj1.initName ("FileAttachment"));

  Object obj2;
  obj2.initString(filename->copy());
  annotObj.dictSet ("FS", &obj2);

  initialize(docA, annotObj.getDict());
}

AnnotFileAttachment::AnnotFileAttachment(PDFDoc *docA, Dict *dict, Object *obj) :
  AnnotMarkup(docA, dict, obj) {
  type = typeFileAttachment;
  initialize(docA, dict);
}

AnnotFileAttachment::~AnnotFileAttachment() {
  file.free();

  if (name)
    delete name;
}

void AnnotFileAttachment::initialize(PDFDoc *docA, Dict* dict) {
  Object obj1;

  if (dict->lookup("FS", &obj1)->isDict() || dict->lookup("FS", &obj1)->isString()) {
    obj1.copy(&file);
  } else {
    error(errSyntaxError, -1, "Bad Annot File Attachment");
    ok = gFalse;
  }
  obj1.free();

  if (dict->lookup("Name", &obj1)->isName()) {
    name = new GooString(obj1.getName());
  } else {
    name = new GooString("PushPin");
  }
  obj1.free();
}

#define ANNOT_FILE_ATTACHMENT_AP_PUSHPIN                                         \
  "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n"       \
  "2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n"     \
  "l 1 21.523 2.477 23 4.301 23 c h\n"                                           \
  "4.301 23 m f\n"                                                               \
  "0.533333 0.541176 0.521569 RG 2 w\n"                                          \
  "1 J\n"                                                                        \
  "1 j\n"                                                                        \
  "[] 0.0 d\n"                                                                   \
  "4 M 5 4 m 6 5 l S\n"                                                          \
  "2 w\n"                                                                        \
  "11 14 m 9 12 l 6 12 l 13 5 l 13 8 l 15 10 l 18 11 l 20 11 l 12 19 l 12\n"     \
  "17 l 11 14 l h\n"                                                             \
  "11 14 m S\n"                                                                  \
  "3 w\n"                                                                        \
  "6 5 m 9 8 l S\n"                                                              \
  "0.729412 0.741176 0.713725 RG 2 w\n"                                          \
  "5 5 m 6 6 l S\n"                                                              \
  "2 w\n"                                                                        \
  "11 15 m 9 13 l 6 13 l 13 6 l 13 9 l 15 11 l 18 12 l 20 12 l 12 20 l 12\n"     \
  "18 l 11 15 l h\n"                                                             \
  "11 15 m S\n"                                                                  \
  "3 w\n"                                                                        \
  "6 6 m 9 9 l S\n"

#define ANNOT_FILE_ATTACHMENT_AP_PAPERCLIP                                       \
  "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n"       \
  "2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n"     \
  "l 1 21.523 2.477 23 4.301 23 c h\n"                                           \
  "4.301 23 m f\n"                                                               \
  "0.533333 0.541176 0.521569 RG 2 w\n"                                          \
  "1 J\n"                                                                        \
  "1 j\n"                                                                        \
  "[] 0.0 d\n"                                                                   \
  "4 M 16.645 12.035 m 12.418 7.707 l 10.902 6.559 6.402 11.203 8.09 12.562 c\n" \
  "14.133 18.578 l 14.949 19.387 16.867 19.184 17.539 18.465 c 20.551\n"         \
  "15.23 l 21.191 14.66 21.336 12.887 20.426 12.102 c 13.18 4.824 l 12.18\n"     \
  "3.82 6.25 2.566 4.324 4.461 c 3 6.395 3.383 11.438 4.711 12.801 c 9.648\n"    \
  "17.887 l S\n"                                                                 \
  "0.729412 0.741176 0.713725 RG 16.645 13.035 m 12.418 8.707 l\n"               \
  "10.902 7.559 6.402 12.203 8.09 13.562 c\n"                                    \
  "14.133 19.578 l 14.949 20.387 16.867 20.184 17.539 19.465 c 20.551\n"         \
  "16.23 l 21.191 15.66 21.336 13.887 20.426 13.102 c 13.18 5.824 l 12.18\n"     \
  "4.82 6.25 3.566 4.324 5.461 c 3 7.395 3.383 12.438 4.711 13.801 c 9.648\n"    \
  "18.887 l S\n"

#define ANNOT_FILE_ATTACHMENT_AP_GRAPH                                           \
  "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n"       \
  "2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n"     \
  "l 1 21.523 2.477 23 4.301 23 c h\n"                                           \
  "4.301 23 m f\n"                                                               \
  "0.533333 0.541176 0.521569 RG 1 w\n"                                          \
  "1 J\n"                                                                        \
  "0 j\n"                                                                        \
  "[] 0.0 d\n"                                                                   \
  "4 M 18.5 15.5 m 18.5 13.086 l 16.086 15.5 l 18.5 15.5 l h\n"                  \
  "18.5 15.5 m S\n"                                                              \
  "7 7 m 10 11 l 13 9 l 18 15 l S\n"                                             \
  "0.729412 0.741176 0.713725 RG 7 8 m 10 12 l 13 10 l 18 16 l S\n"              \
  "18.5 16.5 m 18.5 14.086 l 16.086 16.5 l 18.5 16.5 l h\n"                      \
  "18.5 16.5 m S\n"                                                              \
  "0.533333 0.541176 0.521569 RG 2 w\n"                                          \
  "1 j\n"                                                                        \
  "3 19 m 3 3 l 21 3 l S\n"                                                      \
  "0.729412 0.741176 0.713725 RG 3 20 m 3 4 l 21 4 l S\n"

#define ANNOT_FILE_ATTACHMENT_AP_TAG                                             \
  "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n"       \
  "2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n"     \
  "l 1 21.523 2.477 23 4.301 23 c h\n"                                           \
  "4.301 23 m f\n"                                                               \
  "0.533333 0.541176 0.521569 RG 0.999781 w\n"                                   \
  "1 J\n"                                                                        \
  "1 j\n"                                                                        \
  "[] 0.0 d\n"                                                                   \
  "4 M q 1 0 0 -1 0 24 cm\n"                                                     \
  "8.492 8.707 m 8.492 9.535 7.82 10.207 6.992 10.207 c 6.164 10.207 5.492\n"    \
  "9.535 5.492 8.707 c 5.492 7.879 6.164 7.207 6.992 7.207 c 7.82 7.207\n"       \
  "8.492 7.879 8.492 8.707 c h\n"                                                \
  "8.492 8.707 m S Q\n"                                                          \
  "2 w\n"                                                                        \
  "20.078 11.414 m 20.891 10.602 20.785 9.293 20.078 8.586 c 14.422 2.93 l\n"    \
  "13.715 2.223 12.301 2.223 11.594 2.93 c 3.816 10.707 l 3.109 11.414\n"        \
  "2.402 17.781 3.816 19.195 c 5.23 20.609 11.594 19.902 12.301 19.195 c\n"      \
  "20.078 11.414 l h\n"                                                          \
  "20.078 11.414 m S\n"                                                          \
  "0.729412 0.741176 0.713725 RG 20.078 12.414 m\n"                              \
  "20.891 11.605 20.785 10.293 20.078 9.586 c 14.422 3.93 l\n"                   \
  "13.715 3.223 12.301 3.223 11.594 3.93 c 3.816 11.707 l 3.109 12.414\n"        \
  "2.402 18.781 3.816 20.195 c 5.23 21.609 11.594 20.902 12.301 20.195 c\n"      \
  "20.078 12.414 l h\n"                                                          \
  "20.078 12.414 m S\n"                                                          \
  "0.533333 0.541176 0.521569 RG 1 w\n"                                          \
  "0 j\n"                                                                        \
  "11.949 13.184 m 16.191 8.941 l S\n"                                           \
  "0.729412 0.741176 0.713725 RG 11.949 14.184 m 16.191 9.941 l S\n"             \
  "0.533333 0.541176 0.521569 RG 14.07 6.82 m 9.828 11.062 l S\n"                \
  "0.729412 0.741176 0.713725 RG 14.07 7.82 m 9.828 12.062 l S\n"                \
  "0.533333 0.541176 0.521569 RG 6.93 15.141 m 8 20 14.27 20.5 16 20.5 c\n"      \
  "18.094 20.504 19.5 20 19.5 18 c 19.5 16.699 20.91 16.418 22.5 16.5 c S\n"     \
  "0.729412 0.741176 0.713725 RG 0.999781 w\n"                                   \
  "1 j\n"                                                                        \
  "q 1 0 0 -1 0 24 cm\n"                                                         \
  "8.492 7.707 m 8.492 8.535 7.82 9.207 6.992 9.207 c 6.164 9.207 5.492\n"       \
  "8.535 5.492 7.707 c 5.492 6.879 6.164 6.207 6.992 6.207 c 7.82 6.207\n"       \
  "8.492 6.879 8.492 7.707 c h\n"                                                \
  "8.492 7.707 m S Q\n"                                                          \
  "1 w\n"                                                                        \
  "0 j\n"                                                                        \
  "6.93 16.141 m 8 21 14.27 21.5 16 21.5 c 18.094 21.504 19.5 21 19.5 19 c\n"    \
  "19.5 17.699 20.91 17.418 22.5 17.5 c S\n"

void AnnotFileAttachment::draw(Gfx *gfx, GBool printing) {
  Object obj;
  double ca = 1;

  if (!isVisible (printing))
    return;

  annotLocker();
  if (appearance.isNull()) {
    ca = opacity;

    appearBuf = new GooString ();

    appearBuf->append ("q\n");
    if (color)
      setColor(color, gTrue);
    else
      appearBuf->append ("1 1 1 rg\n");
    if (!name->cmp("PushPin"))
      appearBuf->append (ANNOT_FILE_ATTACHMENT_AP_PUSHPIN);
    else if (!name->cmp("Paperclip"))
      appearBuf->append (ANNOT_FILE_ATTACHMENT_AP_PAPERCLIP);
    else if (!name->cmp("Graph"))
      appearBuf->append (ANNOT_FILE_ATTACHMENT_AP_GRAPH);
    else if (!name->cmp("Tag"))
      appearBuf->append (ANNOT_FILE_ATTACHMENT_AP_TAG);
    appearBuf->append ("Q\n");

    double bbox[4];
    bbox[0] = bbox[1] = 0;
    bbox[2] = bbox[3] = 24;
    if (ca == 1) {
      createForm (bbox, gFalse, NULL, &appearance);
    } else {
      Object aStream;

      createForm (bbox, gTrue, NULL, &aStream);
      delete appearBuf;

      Object resDict;
      appearBuf = new GooString ("/GS0 gs\n/Fm0 Do");
      createResourcesDict("Fm0", &aStream, "GS0", ca, NULL, &resDict);
      createForm(bbox, gFalse, &resDict, &appearance);
    }
    delete appearBuf;
  }

  // draw the appearance stream
  appearance.fetch(gfx->getXRef(), &obj);
  gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color,
		 rect->x1, rect->y1, rect->x2, rect->y2, getRotation());
  obj.free();
}

//------------------------------------------------------------------------
// AnnotSound
//------------------------------------------------------------------------
AnnotSound::AnnotSound(PDFDoc *docA, PDFRectangle *rect, Sound *soundA) :
    AnnotMarkup(docA, rect) {
  Object obj1;

  type = typeSound;

  annotObj.dictSet ("Subtype", obj1.initName ("Sound"));

  Object obj2;
  Stream *str = soundA->getStream();
  obj2.initStream (str);
  str->incRef(); //FIXME: initStream should do this?
  annotObj.dictSet ("Sound", &obj2);

  initialize(docA, annotObj.getDict());
}

AnnotSound::AnnotSound(PDFDoc *docA, Dict *dict, Object *obj) :
  AnnotMarkup(docA, dict, obj) {
  type = typeSound;
  initialize(docA, dict);
}

AnnotSound::~AnnotSound() {
  delete sound;

  delete name;
}

void AnnotSound::initialize(PDFDoc *docA, Dict* dict) {
  Object obj1;

  sound = Sound::parseSound(dict->lookup("Sound", &obj1));
  if (!sound) {
    error(errSyntaxError, -1, "Bad Annot Sound");
    ok = gFalse;
  }
  obj1.free();

  if (dict->lookup("Name", &obj1)->isName()) {
    name = new GooString(obj1.getName());
  } else {
    name = new GooString("Speaker");
  }
  obj1.free();
}

#define ANNOT_SOUND_AP_SPEAKER                                               \
  "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n"   \
  "2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n" \
  "l 1 21.523 2.477 23 4.301 23 c h\n"                                       \
  "4.301 23 m f\n"                                                           \
  "0.533333 0.541176 0.521569 RG 2 w\n"                                      \
  "0 J\n"                                                                    \
  "1 j\n"                                                                    \
  "[] 0.0 d\n"                                                               \
  "4 M 4 14 m 4.086 8.043 l 7 8 l 11 4 l 11 18 l 7 14 l 4 14 l h\n"          \
  "4 14 m S\n"                                                               \
  "1 w\n"                                                                    \
  "1 J\n"                                                                    \
  "0 j\n"                                                                    \
  "13.699 15.398 m 14.699 13.398 14.699 9.398 13.699 7.398 c S\n"            \
  "18.199 19.398 m 21.199 17.398 21.199 5.398 18.199 3.398 c S\n"            \
  "16 17.398 m 18 16.398 18 7.398 16 5.398 c S\n"                            \
  "0.729412 0.741176 0.713725 RG 2 w\n"                                      \
  "0 J\n"                                                                    \
  "1 j\n"                                                                    \
  "4 15 m 4.086 9.043 l 7 9 l 11 5 l 11 19 l 7 15 l 4 15 l h\n"              \
  "4 15 m S\n"                                                               \
  "1 w\n"                                                                    \
  "1 J\n"                                                                    \
  "0 j\n"                                                                    \
  "13.699 16 m 14.699 14 14.699 10 13.699 8 c S\n"                           \
  "18.199 20 m 21.199 18 21.199 6 18.199 4 c S\n"                            \
  "16 18 m 18 17 18 8 16 6 c S\n"

#define ANNOT_SOUND_AP_MIC                                                        \
  "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n"        \
  "2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n"      \
  "l 1 21.523 2.477 23 4.301 23 c h\n"                                            \
  "4.301 23 m f\n"                                                                \
  "0.533333 0.541176 0.521569 RG 2 w\n"                                           \
  "1 J\n"                                                                         \
  "0 j\n"                                                                         \
  "[] 0.0 d\n"                                                                    \
  "4 M 12 20 m 12 20 l 13.656 20 15 18.656 15 17 c 15 13 l 15 11.344 13.656 10\n" \
  "12 10 c 12 10 l 10.344 10 9 11.344 9 13 c 9 17 l 9 18.656 10.344 20 12\n"      \
  "20 c h\n"                                                                      \
  "12 20 m S\n"                                                                   \
  "1 w\n"                                                                         \
  "17.5 14.5 m 17.5 11.973 l 17.5 8.941 15.047 6.5 12 6.5 c 8.953 6.5 6.5\n"      \
  "8.941 6.5 11.973 c 6.5 14.5 l S\n"                                             \
  "2 w\n"                                                                         \
  "0 J\n"                                                                         \
  "12 6.52 m 12 3 l S\n"                                                          \
  "1 J\n"                                                                         \
  "8 3 m 16 3 l S\n"                                                              \
  "0.729412 0.741176 0.713725 RG 12 21 m 12 21 l 13.656 21 15 19.656 15 18 c\n"   \
  "15 14 l 15 12.344 13.656 11 12 11 c 12 11 l 10.344 11 9 12.344 9 14 c\n"       \
  "9 18 l 9 19.656 10.344 21 12 21 c h\n"                                         \
  "12 21 m S\n"                                                                   \
  "1 w\n"                                                                         \
  "17.5 15.5 m 17.5 12.973 l 17.5 9.941 15.047 7.5 12 7.5 c 8.953 7.5 6.5\n"      \
  "9.941 6.5 12.973 c 6.5 15.5 l S\n"                                             \
  "2 w\n"                                                                         \
  "0 J\n"                                                                         \
  "12 7.52 m 12 4 l S\n"                                                          \
  "1 J\n"                                                                         \
  "8 4 m 16 4 l S\n"

void AnnotSound::draw(Gfx *gfx, GBool printing) {
  Object obj;
  double ca = 1;

  if (!isVisible (printing))
    return;

  annotLocker();
  if (appearance.isNull()) {
    ca = opacity;

    appearBuf = new GooString ();

    appearBuf->append ("q\n");
    if (color)
      setColor(color, gTrue);
    else
      appearBuf->append ("1 1 1 rg\n");
    if (!name->cmp("Speaker"))
      appearBuf->append (ANNOT_SOUND_AP_SPEAKER);
    else if (!name->cmp("Mic"))
      appearBuf->append (ANNOT_SOUND_AP_MIC);
    appearBuf->append ("Q\n");

    double bbox[4];
    bbox[0] = bbox[1] = 0;
    bbox[2] = bbox[3] = 24;
    if (ca == 1) {
      createForm(bbox, gFalse, NULL, &appearance);
    } else {
      Object aStream, resDict;

      createForm(bbox, gTrue, NULL, &aStream);
      delete appearBuf;

      appearBuf = new GooString ("/GS0 gs\n/Fm0 Do");
      createResourcesDict("Fm0", &aStream, "GS0", ca, NULL, &resDict);
      createForm(bbox, gFalse, &resDict, &appearance);
    }
    delete appearBuf;
  }

  // draw the appearance stream
  appearance.fetch(gfx->getXRef(), &obj);
  gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color,
		 rect->x1, rect->y1, rect->x2, rect->y2, getRotation());
  obj.free();
}

//------------------------------------------------------------------------
// Annot3D
//------------------------------------------------------------------------
Annot3D::Annot3D(PDFDoc *docA, PDFRectangle *rect) :
    Annot(docA, rect) {
  Object obj1;

  type = type3D;

  annotObj.dictSet ("Subtype", obj1.initName ("3D"));

  initialize(docA, annotObj.getDict());
}

Annot3D::Annot3D(PDFDoc *docA, Dict *dict, Object *obj) :
  Annot(docA, dict, obj) {
  type = type3D;
  initialize(docA, dict);
}

Annot3D::~Annot3D() {
  if (activation)
    delete activation;
}

void Annot3D::initialize(PDFDoc *docA, Dict* dict) {
  Object obj1;

  if (dict->lookup("3DA", &obj1)->isDict()) {
    activation = new Activation(obj1.getDict());
  } else {
    activation = NULL;
  }
  obj1.free();
}

Annot3D::Activation::Activation(Dict *dict) {
  Object obj1;

  if (dict->lookup("A", &obj1)->isName()) {
    const char *name = obj1.getName();

    if(!strcmp(name, "PO")) {
      aTrigger = aTriggerPageOpened;
    } else if(!strcmp(name, "PV")) {
      aTrigger = aTriggerPageVisible;
    } else if(!strcmp(name, "XA")) {
      aTrigger = aTriggerUserAction;
    } else {
      aTrigger = aTriggerUnknown;
    }
  } else {
    aTrigger = aTriggerUnknown;
  }
  obj1.free();

  if(dict->lookup("AIS", &obj1)->isName()) {
    const char *name = obj1.getName();

    if(!strcmp(name, "I")) {
      aState = aStateEnabled;
    } else if(!strcmp(name, "L")) {
      aState = aStateDisabled;
    } else {
      aState = aStateUnknown;
    }
  } else {
    aState = aStateUnknown;
  }
  obj1.free();

  if(dict->lookup("D", &obj1)->isName()) {
    const char *name = obj1.getName();

    if(!strcmp(name, "PC")) {
      dTrigger = dTriggerPageClosed;
    } else if(!strcmp(name, "PI")) {
      dTrigger = dTriggerPageInvisible;
    } else if(!strcmp(name, "XD")) {
      dTrigger = dTriggerUserAction;
    } else {
      dTrigger = dTriggerUnknown;
    }
  } else {
    dTrigger = dTriggerUnknown;
  }
  obj1.free();

  if(dict->lookup("DIS", &obj1)->isName()) {
    const char *name = obj1.getName();

    if(!strcmp(name, "U")) {
      dState = dStateUninstantiaded;
    } else if(!strcmp(name, "I")) {
      dState = dStateInstantiated;
    } else if(!strcmp(name, "L")) {
      dState = dStateLive;
    } else {
      dState = dStateUnknown;
    }
  } else {
    dState = dStateUnknown;
  }
  obj1.free();

  if (dict->lookup("TB", &obj1)->isBool()) {
    displayToolbar = obj1.getBool();
  } else {
    displayToolbar = gTrue;
  }
  obj1.free();

  if (dict->lookup("NP", &obj1)->isBool()) {
    displayNavigation = obj1.getBool();
  } else {
    displayNavigation = gFalse;
  }
  obj1.free();
}

//------------------------------------------------------------------------
// AnnotRichMedia
//------------------------------------------------------------------------
AnnotRichMedia::AnnotRichMedia(PDFDoc *docA, PDFRectangle *rect) :
    Annot(docA, rect) {
  Object obj1;

  type = typeRichMedia;

  annotObj.dictSet ("Subtype", obj1.initName ("RichMedia"));

  initialize(docA, annotObj.getDict());
}

AnnotRichMedia::AnnotRichMedia(PDFDoc *docA, Dict *dict, Object *obj) :
  Annot(docA, dict, obj) {
  type = typeRichMedia;
  initialize(docA, dict);
}

AnnotRichMedia::~AnnotRichMedia() {
  delete content;
  delete settings;
}

void AnnotRichMedia::initialize(PDFDoc *docA, Dict* dict) {
  Object obj1;

  if (dict->lookup("RichMediaContent", &obj1)->isDict()) {
    content = new AnnotRichMedia::Content(obj1.getDict());
  } else {
    content = NULL;
  }
  obj1.free();

  if (dict->lookup("RichMediaSettings", &obj1)->isDict()) {
    settings = new AnnotRichMedia::Settings(obj1.getDict());
  } else {
    settings = NULL;
  }
  obj1.free();
}

AnnotRichMedia::Content* AnnotRichMedia::getContent() const {
  return content;
}

AnnotRichMedia::Settings* AnnotRichMedia::getSettings() const {
  return settings;
}

AnnotRichMedia::Settings::Settings(Dict *dict) {
  Object obj1;

  if (dict->lookup("Activation", &obj1)->isDict()) {
    activation = new AnnotRichMedia::Activation(obj1.getDict());
  } else {
    activation = NULL;
  }
  obj1.free();

  if (dict->lookup("Deactivation", &obj1)->isDict()) {
    deactivation = new AnnotRichMedia::Deactivation(obj1.getDict());
  } else {
    deactivation = NULL;
  }
  obj1.free();
}

AnnotRichMedia::Settings::~Settings() {
  delete activation;
  delete deactivation;
}

AnnotRichMedia::Activation* AnnotRichMedia::Settings::getActivation() const {
  return activation;
}

AnnotRichMedia::Deactivation* AnnotRichMedia::Settings::getDeactivation() const {
  return deactivation;
}

AnnotRichMedia::Activation::Activation(Dict *dict) {
  Object obj1;

  if (dict->lookup("Condition", &obj1)->isName()) {
    const char *name = obj1.getName();

    if (!strcmp(name, "PO")) {
      condition = conditionPageOpened;
    } else if (!strcmp(name, "PV")) {
      condition = conditionPageVisible;
    } else if (!strcmp(name, "XA")) {
      condition = conditionUserAction;
    } else {
      condition = conditionUserAction;
    }
  } else {
    condition = conditionUserAction;
  }
  obj1.free();
}

AnnotRichMedia::Activation::Condition AnnotRichMedia::Activation::getCondition() const {
  return condition;
}

AnnotRichMedia::Deactivation::Deactivation(Dict *dict) {
  Object obj1;

  if (dict->lookup("Condition", &obj1)->isName()) {
    const char *name = obj1.getName();

    if (!strcmp(name, "PC")) {
      condition = conditionPageClosed;
    } else if (!strcmp(name, "PI")) {
      condition = conditionPageInvisible;
    } else if (!strcmp(name, "XD")) {
      condition = conditionUserAction;
    } else {
      condition = conditionUserAction;
    }
  } else {
    condition = conditionUserAction;
  }
  obj1.free();
}

AnnotRichMedia::Deactivation::Condition AnnotRichMedia::Deactivation::getCondition() const {
  return condition;
}

AnnotRichMedia::Content::Content(Dict *dict) {
  Object obj1;

  if (dict->lookup("Configurations", &obj1)->isArray()) {
    nConfigurations = obj1.arrayGetLength();

    configurations = (Configuration **)gmallocn(nConfigurations, sizeof(Configuration *));

    for (int i = 0; i < nConfigurations; ++i) {
      Object obj2;

      if (obj1.arrayGet(i, &obj2)->isDict()) {
        configurations[i] = new AnnotRichMedia::Configuration(obj2.getDict());
      } else {
        configurations[i] = NULL;
      }
      obj2.free();
    }
  } else {
    nConfigurations = 0;
    configurations = NULL;
  }
  obj1.free();

  nAssets = 0;
  assets = NULL;
  if (dict->lookup("Assets", &obj1)->isDict()) {
    Object obj2;

    if (obj1.getDict()->lookup("Names", &obj2)->isArray()) {
      nAssets = obj2.arrayGetLength() / 2;

      assets = (Asset **)gmallocn(nAssets, sizeof(Asset *));

      int counter = 0;
      for (int i = 0; i < nAssets; ++i) {
        Object objKey;

        assets[counter] = new AnnotRichMedia::Asset;

        obj2.arrayGet(i * 2, &objKey);
        obj2.arrayGet(i * 2 + 1, &assets[counter]->fileSpec);

        assets[counter]->name = new GooString( objKey.getString() );
        ++counter;

        objKey.free();
      }
    }
    obj2.free();
  }
  obj1.free();
}

AnnotRichMedia::Content::~Content() {
  if (configurations) {
    for (int i = 0; i < nConfigurations; ++i)
      delete configurations[i];
    gfree(configurations);
  }

  if (assets) {
    for (int i = 0; i < nAssets; ++i)
      delete assets[i];
    gfree(assets);
  }
}

int AnnotRichMedia::Content::getConfigurationsCount() const {
  return nConfigurations;
}

AnnotRichMedia::Configuration* AnnotRichMedia::Content::getConfiguration(int index) const {
  if (index < 0 || index >= nConfigurations)
    return NULL;

  return configurations[index];
}

int AnnotRichMedia::Content::getAssetsCount() const {
  return nAssets;
}

AnnotRichMedia::Asset* AnnotRichMedia::Content::getAsset(int index) const {
  if (index < 0 || index >= nAssets)
    return NULL;

  return assets[index];
}

AnnotRichMedia::Asset::Asset()
  : name(NULL)
{
}

AnnotRichMedia::Asset::~Asset()
{
  delete name;
  fileSpec.free();
}

GooString* AnnotRichMedia::Asset::getName() const {
  return name;
}

Object* AnnotRichMedia::Asset::getFileSpec() const {
  return const_cast<Object*>(&fileSpec);
}

AnnotRichMedia::Configuration::Configuration(Dict *dict)
{
  Object obj1;

  if (dict->lookup("Instances", &obj1)->isArray()) {
    nInstances = obj1.arrayGetLength();

    instances = (Instance **)gmallocn(nInstances, sizeof(Instance *));

    for (int i = 0; i < nInstances; ++i) {
      Object obj2;

      if (obj1.arrayGet(i, &obj2)->isDict()) {
        instances[i] = new AnnotRichMedia::Instance(obj2.getDict());
      } else {
        instances[i] = NULL;
      }
      obj2.free();
    }
  } else {
    instances = NULL;
  }
  obj1.free();

  if (dict->lookup("Name", &obj1)->isString()) {
    name = new GooString(obj1.getString());
  } else {
    name = NULL;
  }
  obj1.free();

  if (dict->lookup("Subtype", &obj1)->isName()) {
    const char *name = obj1.getName();

    if (!strcmp(name, "3D")) {
      type = type3D;
    } else if (!strcmp(name, "Flash")) {
      type = typeFlash;
    } else if (!strcmp(name, "Sound")) {
      type = typeSound;
    } else if (!strcmp(name, "Video")) {
      type = typeVideo;
    } else {
      // determine from first non null instance
      type = typeFlash; // default in case all instances are null
      if (instances && nInstances > 0) {
	for (int i = 0; i < nInstances; ++i) {
	  AnnotRichMedia::Instance *instance = instances[i];
	  if (instance) {
	    switch (instance->getType()) {
	      case AnnotRichMedia::Instance::type3D:
		type = type3D;
		break;
	      case AnnotRichMedia::Instance::typeFlash:
		type = typeFlash;
		break;
	      case AnnotRichMedia::Instance::typeSound:
		type = typeSound;
		break;
	      case AnnotRichMedia::Instance::typeVideo:
		type = typeVideo;
		break;
	    }
	    // break the loop since we found the first non null instance
	    break;
	  }
	}
      }
    }
  }
  obj1.free();
}

AnnotRichMedia::Configuration::~Configuration()
{
  if (instances) {
    for (int i = 0; i < nInstances; ++i)
      delete instances[i];
    gfree(instances);
  }

  delete name;
}

int AnnotRichMedia::Configuration::getInstancesCount() const {
  return nInstances;
}

AnnotRichMedia::Instance* AnnotRichMedia::Configuration::getInstance(int index) const {
  if (index < 0 || index >= nInstances)
    return NULL;

  return instances[index];
}

GooString* AnnotRichMedia::Configuration::getName() const {
  return name;
}

AnnotRichMedia::Configuration::Type AnnotRichMedia::Configuration::getType() const {
  return type;
}

AnnotRichMedia::Instance::Instance(Dict *dict)
{
  Object obj1;

  dict->lookup("Subtype", &obj1);
  const char *name = obj1.isName() ? obj1.getName() : "";

  if (!strcmp(name, "3D")) {
    type = type3D;
  } else if (!strcmp(name, "Flash")) {
    type = typeFlash;
  } else if (!strcmp(name, "Sound")) {
    type = typeSound;
  } else if (!strcmp(name, "Video")) {
    type = typeVideo;
  } else {
    type = typeFlash;
  }
  obj1.free();

  if (dict->lookup("Params", &obj1)->isDict()) {
    params = new AnnotRichMedia::Params(obj1.getDict());
  } else {
    params = NULL;
  }
  obj1.free();
}

AnnotRichMedia::Instance::~Instance()
{
  delete params;
}

AnnotRichMedia::Instance::Type AnnotRichMedia::Instance::getType() const {
  return type;
}

AnnotRichMedia::Params* AnnotRichMedia::Instance::getParams() const {
  return params;
}

AnnotRichMedia::Params::Params(Dict *dict)
{
  Object obj1;

  if (dict->lookup("FlashVars", &obj1)->isString()) {
    flashVars = new GooString(obj1.getString());
  } else {
    flashVars = NULL;
  }
  obj1.free();
}

AnnotRichMedia::Params::~Params()
{
  delete flashVars;
}

GooString* AnnotRichMedia::Params::getFlashVars() const {
  return flashVars;
}

//------------------------------------------------------------------------
// Annots
//------------------------------------------------------------------------

Annots::Annots(PDFDoc *docA, int page, Object *annotsObj) {
  Annot *annot;
  Object obj1;
  int i;

  doc = docA;
  annots = NULL;
  size = 0;
  nAnnots = 0;

  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;
      if (annotsObj->arrayGet(i, &obj1)->isDict()) {
        annotsObj->arrayGetNF(i, &obj2);
        annot = createAnnot (obj1.getDict(), &obj2);
        if (annot) {
          if (annot->isOk()) {
            annot->setPage(page, gFalse); // Don't change /P
            appendAnnot(annot);
          }
          annot->decRefCnt();
        }
      }
      obj2.free();
      obj1.free();
    }
  }
}

void Annots::appendAnnot(Annot *annot) {
  if (annot && annot->isOk()) {
    if (nAnnots >= size) {
      size += 16;
      annots = (Annot **)greallocn(annots, size, sizeof(Annot *));
    }
    annots[nAnnots++] = annot;
    annot->incRefCnt();
  }
}

GBool Annots::removeAnnot(Annot *annot) {
  int idx = -1;
  // Search annot and determine its index
  for (int i = 0; idx == -1 && i < nAnnots; i++) {
    if (annots[i] == annot) {
      idx = i;
    }
  }
  if (idx == -1) {
    return gFalse;
  } else {
    --nAnnots;
    memmove( annots + idx, annots + idx + 1, sizeof(annots[0]) * (nAnnots - idx) );
    annot->decRefCnt();
    return gTrue;
  }
}

Annot *Annots::createAnnot(Dict* dict, Object *obj) {
  Annot *annot = NULL;
  Object obj1;

  if (dict->lookup("Subtype", &obj1)->isName()) {
    const char *typeName = obj1.getName();

    if (!strcmp(typeName, "Text")) {
      annot = new AnnotText(doc, dict, obj);
    } else if (!strcmp(typeName, "Link")) {
      annot = new AnnotLink(doc, dict, obj);
    } else if (!strcmp(typeName, "FreeText")) {
      annot = new AnnotFreeText(doc, dict, obj);
    } else if (!strcmp(typeName, "Line")) {
      annot = new AnnotLine(doc, dict, obj);
    } else if (!strcmp(typeName, "Square")) {
      annot = new AnnotGeometry(doc, dict, obj);
    } else if (!strcmp(typeName, "Circle")) {
      annot = new AnnotGeometry(doc, dict, obj);
    } else if (!strcmp(typeName, "Polygon")) {
      annot = new AnnotPolygon(doc, dict, obj);
    } else if (!strcmp(typeName, "PolyLine")) {
      annot = new AnnotPolygon(doc, dict, obj);
    } else if (!strcmp(typeName, "Highlight")) {
      annot = new AnnotTextMarkup(doc, dict, obj);
    } else if (!strcmp(typeName, "Underline")) {
      annot = new AnnotTextMarkup(doc, dict, obj);
    } else if (!strcmp(typeName, "Squiggly")) {
      annot = new AnnotTextMarkup(doc, dict, obj);
    } else if (!strcmp(typeName, "StrikeOut")) {
      annot = new AnnotTextMarkup(doc, dict, obj);
    } else if (!strcmp(typeName, "Stamp")) {
      annot = new AnnotStamp(doc, dict, obj);
    } else if (!strcmp(typeName, "Caret")) {
      annot = new AnnotCaret(doc, dict, obj);
    } else if (!strcmp(typeName, "Ink")) {
      annot = new AnnotInk(doc, dict, obj);
    } else if (!strcmp(typeName, "FileAttachment")) {
      annot = new AnnotFileAttachment(doc, dict, obj);
    } else if (!strcmp(typeName, "Sound")) {
      annot = new AnnotSound(doc, dict, obj);
    } else if(!strcmp(typeName, "Movie")) {
      annot = new AnnotMovie(doc, dict, obj);
    } else if(!strcmp(typeName, "Widget")) {
      // Find the annot in forms
      if (obj->isRef()) {
        Form *form = doc->getCatalog()->getForm();
        if (form) {
          FormWidget *widget = form->findWidgetByRef(obj->getRef());
          if (widget) {
            annot = widget->getWidgetAnnotation();
            annot->incRefCnt();
          }
        }
      }
      if (!annot)
        annot = new AnnotWidget(doc, dict, obj);
    } else if(!strcmp(typeName, "Screen")) {
      annot = new AnnotScreen(doc, dict, obj);
    } else if(!strcmp(typeName, "PrinterMark")) {
      annot = new Annot(doc, dict, obj);
    } else if (!strcmp(typeName, "TrapNet")) {
      annot = new Annot(doc, dict, obj);
    } else if (!strcmp(typeName, "Watermark")) {
      annot = new Annot(doc, dict, obj);
    } else if (!strcmp(typeName, "3D")) {
      annot = new Annot3D(doc, dict, obj);
    } else if (!strcmp(typeName, "RichMedia")) {
      annot = new AnnotRichMedia(doc, dict, obj);
    } else if (!strcmp(typeName, "Popup")) {
      /* Popup annots are already handled by markup annots
       * Here we only care about popup annots without a
       * markup annotation associated
       */
      Object obj2;

      if (dict->lookup("Parent", &obj2)->isNull())
        annot = new AnnotPopup(doc, dict, obj);
      else
        annot = NULL;
      
      obj2.free();
    } else {
      annot = new Annot(doc, dict, obj);
    }
  }
  obj1.free();

  return annot;
}

Annot *Annots::findAnnot(Ref *ref) {
  int i;

  for (i = 0; i < nAnnots; ++i) {
    if (annots[i]->match(ref)) {
      return annots[i];
    }
  }
  return NULL;
}


Annots::~Annots() {
  int i;

  for (i = 0; i < nAnnots; ++i) {
    annots[i]->decRefCnt();
  }
  gfree(annots);
}
