Index: poppler/Annot.cc =================================================================== RCS file: /cvs/poppler/poppler/poppler/Annot.cc,v retrieving revision 1.12 diff -u -r1.12 Annot.cc --- poppler/Annot.cc 21 May 2007 21:29:10 -0000 1.12 +++ poppler/Annot.cc 28 May 2007 22:52:42 -0000 @@ -26,10 +26,7 @@ #include "CharCodeToUnicode.h" #include "Form.h" #include "Error.h" - -#define annotFlagHidden 0x0002 -#define annotFlagPrint 0x0004 -#define annotFlagNoView 0x0020 +#include "Page.h" #define fieldFlagReadOnly 0x00000001 #define fieldFlagRequired 0x00000002 @@ -59,450 +56,984 @@ // = (4 * (sqrt(2) - 1) / 3) * r #define bezierCircle 0.55228475 -//------------------------------------------------------------------------ -// AnnotBorderStyle -//------------------------------------------------------------------------ +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; + } +} + +AnnotExternalDataType parseAnnotExternalData(Dict* dict) { + Object obj1; + AnnotExternalDataType type; + + if (dict->lookup("Subtype", &obj1)->isName()) { + GooString *name = new GooString(obj1.getName()); -AnnotBorderStyle::AnnotBorderStyle(AnnotBorderType typeA, double widthA, - double *dashA, int dashLengthA, - double rA, double gA, double bA) { - type = typeA; - width = widthA; - dash = dashA; - dashLength = dashLengthA; - r = rA; - g = gA; - b = bA; -} - -AnnotBorderStyle::~AnnotBorderStyle() { - if (dash) { - gfree(dash); + if(!name->cmp("Markup3D")) { + type = annotExternalDataMarkup3D; + } else { + type = annotExternalDataMarkupUnknown; + } + delete name; + } else { + type = annotExternalDataMarkupUnknown; } + obj1.free(); + + return type; } //------------------------------------------------------------------------ -// Annot +// AnnotQuadPoints //------------------------------------------------------------------------ -Annot::Annot(XRef *xrefA, Dict *acroForm, Dict *dict, const Ref& aref, Catalog* catalog) -{ - hasRef = true; - ref = aref; - initialize (xrefA, acroForm, dict, catalog); +AnnotQuadPoints::AnnotQuadPoints(double x1, double y1, double x2, double y2, + double x3, double y3, double x4, double y4) { + this->x1 = x1; + this->y1 = y1; + this->x2 = x2; + this->y2 = y2; + this->x3 = x3; + this->y3 = y3; + this->x4 = x4; + this->y4 = y4; } -Annot::Annot(XRef *xrefA, Dict *acroForm, Dict *dict, Catalog* catalog) { - hasRef = false; - initialize (xrefA, acroForm, dict, catalog); -} +//------------------------------------------------------------------------ +// AnnotCalloutLine +//------------------------------------------------------------------------ -void Annot::initialize(XRef *xrefA, Dict *acroForm, Dict *dict, Catalog *catalog) { - Object apObj, asObj, obj1, obj2, obj3; - AnnotBorderType borderType; - double borderWidth; - double *borderDash; - int borderDashLength; - double borderR, borderG, borderB; - double t; +AnnotCalloutLine::AnnotCalloutLine(double x1, double y1, double x2, double y2) { + this->x1 = x1; + this->y1 = y1; + this->x2 = x2; + this->y2 = y2; +} - ok = gTrue; - xref = xrefA; - appearBuf = NULL; - fontSize = 0; - type = NULL; - //widget = NULL; - borderStyle = NULL; +//------------------------------------------------------------------------ +// AnnotCalloutMultiLine +//------------------------------------------------------------------------ - //----- parse the type +AnnotCalloutMultiLine::AnnotCalloutMultiLine(double x1, double y1, double x2, + double y2, double x3, double y3) : AnnotCalloutLine(x1, y1, x2, y2) { + this->x3 = x3; + this->y3 = y3; +} - if (dict->lookup("Subtype", &obj1)->isName()) { - type = new GooString(obj1.getName()); - } - obj1.free(); +//------------------------------------------------------------------------ +// AnnotBorderEffect +//------------------------------------------------------------------------ - //----- parse the rectangle +AnnotBorderEffect::AnnotBorderEffect(Dict *dict) { + Object obj1; + + if (dict->lookup("S", &obj1)->isName()) { + GooString *effectName = new GooString(obj1.getName()); - if (dict->lookup("Rect", &obj1)->isArray() && - obj1.arrayGetLength() == 4) { - readArrayNum(&obj1, 0, &xMin); - readArrayNum(&obj1, 1, &yMin); - readArrayNum(&obj1, 2, &xMax); - readArrayNum(&obj1, 3, &yMax); - if (ok) { - if (xMin > xMax) { - t = xMin; xMin = xMax; xMax = t; - } - if (yMin > yMax) { - t = yMin; yMin = yMax; yMax = t; - } - } else { - xMin = yMin = 0; - xMax = yMax = 1; - error(-1, "Bad bounding box for annotation"); - ok = gFalse; - } + if(!effectName->cmp("C")) + effectType = borderEffectCloudy; + else + effectType = borderEffectNoEffect; + delete effectName; } else { - xMin = yMin = 0; - xMax = yMax = 1; - error(-1, "Bad bounding box for annotation"); - ok = gFalse; + effectType = borderEffectNoEffect; } obj1.free(); - //----- get the flags - if (dict->lookup("F", &obj1)->isInt()) { - flags = obj1.getInt(); + + if ((dict->lookup("I", &obj1)->isNum()) && effectType == borderEffectCloudy) { + intensity = obj1.getNum(); } else { - flags = 0; + intensity = 0; } + obj1.free(); +} - //check for hidden annot - hidden = false; - if (dict->lookup("F", &obj3)->isInt()) { - int flags = obj3.getInt(); - if (flags & 0x2) - hidden = true; - } - obj3.free(); +//------------------------------------------------------------------------ +// AnnotBorder +//------------------------------------------------------------------------ - // check if field apperances need to be regenerated - // Only text or choice fields needs to have appearance regenerated - // see section 8.6.2 "Variable Text" of PDFReference - regen = gFalse; - fieldLookup(dict, "FT", &obj3); +AnnotBorder::~AnnotBorder() { + if (dash) + gfree (dash); +} + +//------------------------------------------------------------------------ +// AnnotBorderArray +//------------------------------------------------------------------------ - if (obj3.isName("Tx") || obj3.isName("Ch")) { - if (acroForm) { - acroForm->lookup("NeedAppearances", &obj1); - if (obj1.isBool() && obj1.getBool()) { - regen = gTrue; - } - obj1.free(); - } - } else { - //for other type of annots, lookup for AP and copy it to the appearance stream - Object apObj, asObj; - if (dict->lookup("AP", &apObj)->isDict()) { - if (dict->lookup("AS", &asObj)->isName()) { - if (apObj.dictLookup("N", &obj1)->isDict()) { - if (obj1.dictLookupNF(asObj.getName(), &obj2)->isRef()) { - obj2.copy(&appearance); - ok = gTrue; - } else { - obj2.free(); - if (obj1.dictLookupNF("Off", &obj2)->isRef()) { - obj2.copy(&appearance); - ok = gTrue; - } - } - obj2.free(); - } - obj1.free(); - } else { - if (apObj.dictLookupNF("N", &obj1)->isRef()) { - obj1.copy(&appearance); - ok = gTrue; +AnnotBorderArray::AnnotBorderArray() { + horizontalCorner = 0; + verticalCorner = 0; + width = 1; + dash = NULL; + dashLength = 0; +} + +AnnotBorderArray::AnnotBorderArray(Array *array) { + Object obj1; + int arrayLength = array->getLength(); + + if (arrayLength >= 3) { + if(array->get(0, &obj1)->isNum()) + horizontalCorner = obj1.getNum(); + obj1.free(); + + if(array->get(1, &obj1)->isNum()) + verticalCorner = obj1.getNum(); + obj1.free(); + + if(array->get(2, &obj1)->isNum()) + width = obj1.getNum(); + obj1.free(); + + // TODO: check not all zero ? + if(arrayLength > 3) { + dashLength = array->getLength() - 3; + dash = (double *) gmallocn (dashLength, sizeof (double)); + + for(int i = 0; i < dashLength && i < DASH_LIMIT; i++) { + + if(array->get((i + 3), &obj1)->isNum()) { + dash[i] = obj1.getNum(); + + if (dash[i] < 0) + dash[i] = 0; + + } else { + dash[i] = 0; } obj1.free(); } - asObj.free(); + } else { + dashLength = 0; + dash = NULL; } - apObj.free(); - + } else { + horizontalCorner = 0; + verticalCorner = 0; + width = 1; + dash = NULL; + dashLength = 0; } +} - //----- parse the border style +//------------------------------------------------------------------------ +// AnnotBorderBS +//------------------------------------------------------------------------ - borderType = annotBorderSolid; - borderWidth = 1; - borderDash = NULL; - borderDashLength = 0; - borderR = 0; - borderG = 0; - borderB = 1; - if (dict->lookup("BS", &obj1)->isDict()) { - if (obj1.dictLookup("S", &obj2)->isName()) { - if (obj2.isName("S")) { - borderType = annotBorderSolid; - } else if (obj2.isName("D")) { - borderType = annotBorderDashed; - } else if (obj2.isName("B")) { - borderType = annotBorderBeveled; - } else if (obj2.isName("I")) { - borderType = annotBorderInset; - } else if (obj2.isName("U")) { - borderType = annotBorderUnderlined; - } - } - obj2.free(); - if (obj1.dictLookup("W", &obj2)->isNum()) { - borderWidth = obj2.getNum(); - } - obj2.free(); - if (obj1.dictLookup("D", &obj2)->isArray()) { - borderDashLength = obj2.arrayGetLength(); - borderDash = (double *)gmallocn(borderDashLength, sizeof(double)); - for (int i = 0; i < borderDashLength; ++i) { - if (obj2.arrayGet(i, &obj3)->isNum()) { - borderDash[i] = obj3.getNum(); - } else { - borderDash[i] = 1; - } - obj3.free(); - } - } - obj2.free(); +AnnotBorderBS::AnnotBorderBS() { + style = borderSolid; + width = 1; + dash = NULL; + dashLength = 0; +} + +AnnotBorderBS::AnnotBorderBS(Dict *dict) { + Object obj1; + + if (dict->lookup("W", &obj1)->isNum()) { + width = obj1.getNum(); } else { - obj1.free(); - if (dict->lookup("Border", &obj1)->isArray()) { - if (obj1.arrayGetLength() >= 3) { - if (obj1.arrayGet(2, &obj2)->isNum()) { - borderWidth = obj2.getNum(); - } - obj2.free(); - if (obj1.arrayGetLength() >= 4) { - if (obj1.arrayGet(3, &obj2)->isArray()) { - borderType = annotBorderDashed; - borderDashLength = obj2.arrayGetLength(); - borderDash = (double *)gmallocn(borderDashLength, sizeof(double)); - for (int i = 0; i < borderDashLength; ++i) { - if (obj2.arrayGet(i, &obj3)->isNum()) { - borderDash[i] = obj3.getNum(); - } else { - borderDash[i] = 1; - } - obj3.free(); - } - } else { - // Adobe draws no border at all if the last element is of - // the wrong type. - borderWidth = 0; - } - } - } - } + width = 1; } - if (dict->lookup("C", &obj1)->isArray() && obj1.arrayGetLength() == 3) { - if (obj1.arrayGet(0, &obj2)->isNum()) { - borderR = obj2.getNum(); - } - obj1.free(); - if (obj1.arrayGet(1, &obj2)->isNum()) { - borderG = obj2.getNum(); + obj1.free(); + + if (dict->lookup("S", &obj1)->isName()) { + GooString *styleName = new GooString(obj1.getName()); + + if(!styleName->cmp("Solid")) { + style = borderSolid; + } else if(!styleName->cmp("Dashed")) { + style = borderDashed; + } else if(!styleName->cmp("Beveled")) { + style = borderBeveled; + } else if(!styleName->cmp("Inset")) { + style = borderInset; + } else if(!styleName->cmp("Underlined")) { + style = borderUnderlined; + } else { + style = borderSolid; } - obj1.free(); - if (obj1.arrayGet(2, &obj2)->isNum()) { - borderB = obj2.getNum(); + delete styleName; + } else { + style = borderSolid; + } + obj1.free(); + + // TODO: check not all zero ? + if (dict->lookup("D", &obj1)->isArray()) { + dashLength = obj1.arrayGetLength(); + dash = (double *) gmallocn (dashLength, sizeof (double)); + + for(int i = 0; i < dashLength; i++) { + Object obj2; + + if(obj1.arrayGet(i, &obj2)->isNum()) { + dash[i] = obj2.getNum(); + + if(dash[i] < 0) + dash[i] = 0; + } else { + dash[i] = 0; + } + + obj2.free(); } - obj1.free(); + + } else { + dashLength = 1; + dash = (double *) gmallocn (dashLength, sizeof (double)); + dash[0] = 3; } obj1.free(); - borderStyle = new AnnotBorderStyle(borderType, borderWidth, - borderDash, borderDashLength, - borderR, borderG, borderB); } -void Annot::readArrayNum(Object *pdfArray, int key, double *value) { - Object valueObject; +//------------------------------------------------------------------------ +// AnnotColor +//------------------------------------------------------------------------ - pdfArray->arrayGet(key, &valueObject); - if (valueObject.isNum()) { - *value = valueObject.getNum(); +AnnotColor::AnnotColor() { + length = 0; + values = NULL; +} + +AnnotColor::AnnotColor(Array *array) { + if (array->getLength() < 5) { + length = array->getLength(); + values = (double *) gmallocn (length, sizeof(double)); + + for(int i = 0; i < length; i++) { + Object obj1; + + if(array->get(i, &obj1)->isNum()) { + values[i] = obj1.getNum(); + + if (values[1] < 0 || values[1] > 1) + values[i] = 0; + } else { + values[i] = 0; + } + obj1.free(); + } } else { - *value = 0; - ok = gFalse; + length = 0; + values = NULL; } - valueObject.free(); } -Annot::~Annot() { - if (type) { - delete type; - } - appearance.free(); - if (appearBuf) { - delete appearBuf; - } +AnnotColor::AnnotColorSpace AnnotColor::getSpace() { + return (AnnotColor::AnnotColorSpace) length; +} - if (borderStyle) { - delete borderStyle; - } +double AnnotColor::getValue(int i) { + if(i >= 0 && i < length) + return values[i]; + return 0; +} + +AnnotColor::~AnnotColor() { + if(values) + gfree (values); } -void Annot::generateFieldAppearance(Dict *field, Dict *annot, Dict *acroForm) { - Object mkObj, ftObj, appearDict, drObj, obj1, obj2, obj3; - Dict *mkDict; - MemStream *appearStream; - GfxFontDict *fontDict; - GBool hasCaption; - double w, dx, dy, r; - double *dash; - GooString *caption, *da; - GooString **text; - GBool *selection; - int dashLength, ff, quadding, comb, nOptions, topIdx, i, j; +//------------------------------------------------------------------------ +// AnnotIconFit +//------------------------------------------------------------------------ - //do not regenerate appearance if we don't need to - if (!regen) - return; +AnnotIconFit::AnnotIconFit(Dict* dict) { + Object obj1; + AnnotExternalDataType type; + + if (dict->lookup("SW", &obj1)->isName()) { + GooString *name = new GooString(obj1.getName()); - // must be a Widget annotation - if (type->cmp("Widget")) { - return; - } - appearBuf = new GooString (); - // get the appearance characteristics (MK) dictionary - if (annot->lookup("MK", &mkObj)->isDict()) { - mkDict = mkObj.getDict(); + if(!name->cmp("B")) { + scaleWhen = scaleBigger; + } else if(!name->cmp("S")) { + scaleWhen = scaleSmaller; + } else if(!name->cmp("N")) { + scaleWhen = scaleNever; + } else { + scaleWhen = scaleAlways; + } + delete name; } else { - mkDict = NULL; + scaleWhen = scaleAlways; } - // draw the background - if (mkDict) { - if (mkDict->lookup("BG", &obj1)->isArray() && - obj1.arrayGetLength() > 0) { - setColor(obj1.getArray(), gTrue, 0); - appearBuf->appendf("0 0 {0:.2f} {1:.2f} re f\n", - xMax - xMin, yMax - yMin); + obj1.free(); + + if (dict->lookup("S", &obj1)->isName()) { + GooString *name = new GooString(obj1.getName()); + + if(!name->cmp("A")) { + scale = scaleAnamorphic; + } else { + scale = scaleProportional; } - obj1.free(); + delete name; + } 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(); - // get the field type - fieldLookup(field, "FT", &ftObj); + if (left < 0 || left > 1) + left = 0.5; + + if (bottom < 0 || bottom > 1) + bottom = 0.5; - // get the field flags (Ff) value - if (fieldLookup(field, "Ff", &obj1)->isInt()) { - ff = obj1.getInt(); } else { - ff = 0; + left = bottom = 0.5; } obj1.free(); - - // draw the border - if (mkDict) { - w = borderStyle->getWidth(); - if (w > 0) { - mkDict->lookup("BC", &obj1); - if (!(obj1.isArray() && obj1.arrayGetLength() > 0)) { - mkDict->lookup("BG", &obj1); - } - if (obj1.isArray() && obj1.arrayGetLength() > 0) { - dx = xMax - xMin; - dy = yMax - yMin; - - // radio buttons with no caption have a round border - hasCaption = mkDict->lookup("CA", &obj2)->isString(); - obj2.free(); - if (ftObj.isName("Btn") && (ff & fieldFlagRadio) && !hasCaption) { - r = 0.5 * (dx < dy ? dx : dy); - switch (borderStyle->getType()) { - case annotBorderDashed: - appearBuf->append("["); - borderStyle->getDash(&dash, &dashLength); - for (i = 0; i < dashLength; ++i) { - appearBuf->appendf(" {0:.2f}", dash[i]); - } - appearBuf->append("] 0 d\n"); - // fall through to the solid case - case annotBorderSolid: - case annotBorderUnderlined: - appearBuf->appendf("{0:.2f} w\n", w); - setColor(obj1.getArray(), gFalse, 0); - drawCircle(0.5 * dx, 0.5 * dy, r - 0.5 * w, gFalse); - break; - case annotBorderBeveled: - case annotBorderInset: - appearBuf->appendf("{0:.2f} w\n", 0.5 * w); - setColor(obj1.getArray(), gFalse, 0); - drawCircle(0.5 * dx, 0.5 * dy, r - 0.25 * w, gFalse); - setColor(obj1.getArray(), gFalse, - borderStyle->getType() == annotBorderBeveled ? 1 : -1); - drawCircleTopLeft(0.5 * dx, 0.5 * dy, r - 0.75 * w); - setColor(obj1.getArray(), gFalse, - borderStyle->getType() == annotBorderBeveled ? -1 : 1); - drawCircleBottomRight(0.5 * dx, 0.5 * dy, r - 0.75 * w); - break; - } - - } else { - switch (borderStyle->getType()) { - case annotBorderDashed: - appearBuf->append("["); - borderStyle->getDash(&dash, &dashLength); - for (i = 0; i < dashLength; ++i) { - appearBuf->appendf(" {0:.2f}", dash[i]); - } - appearBuf->append("] 0 d\n"); - // fall through to the solid case - case annotBorderSolid: - appearBuf->appendf("{0:.2f} w\n", w); - setColor(obj1.getArray(), gFalse, 0); - appearBuf->appendf("{0:.2f} {0:.2f} {1:.2f} {2:.2f} re s\n", - 0.5 * w, dx - w, dy - w); - break; - case annotBorderBeveled: - case annotBorderInset: - setColor(obj1.getArray(), gTrue, - borderStyle->getType() == annotBorderBeveled ? 1 : -1); - 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"); - setColor(obj1.getArray(), gTrue, - borderStyle->getType() == annotBorderBeveled ? -1 : 1); - 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 annotBorderUnderlined: - appearBuf->appendf("{0:.2f} w\n", w); - setColor(obj1.getArray(), gFalse, 0); - 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); - } - } - obj1.free(); - } + + if (dict->lookup("FB", &obj1)->isBool()) { + fullyBounds = obj1.getBool(); + } else { + fullyBounds = gFalse; } + obj1.free(); +} - // get the resource dictionary - acroForm->lookup("DR", &drObj); +//------------------------------------------------------------------------ +// AnnotAppearanceCharacs +//------------------------------------------------------------------------ - // build the font dictionary - if (drObj.isDict() && drObj.dictLookup("Font", &obj1)->isDict()) { - fontDict = new GfxFontDict(xref, NULL, obj1.getDict()); +AnnotAppearanceCharacs::AnnotAppearanceCharacs(Dict *dict) { + Object obj1; + + if (dict->lookup("R", &obj1)->isInt()) { + rotation = obj1.getInt(); } else { - fontDict = NULL; + rotation = 0; } obj1.free(); - // get the default appearance string - if (fieldLookup(field, "DA", &obj1)->isNull()) { - obj1.free(); - acroForm->lookup("DA", &obj1); - } - if (obj1.isString()) { - da = obj1.getString()->copy(); - //TODO: look for a font size / name HERE - // => create a function + if (dict->lookup("BC", &obj1)->isArray()) { + borderColor = new AnnotColor(obj1.getArray()); } else { - da = NULL; + borderColor = NULL; } obj1.free(); - + + if (dict->lookup("BG", &obj1)->isArray()) { + backColor = new AnnotColor(obj1.getArray()); + } else { + backColor = NULL; + } + obj1.free(); + + if (dict->lookup("CA", &obj1)->isName()) { + normalCaption = new GooString(obj1.getName()); + } else { + normalCaption = NULL; + } + obj1.free(); + + if (dict->lookup("RC", &obj1)->isName()) { + rolloverCaption = new GooString(obj1.getName()); + } else { + rolloverCaption = NULL; + } + obj1.free(); + + if (dict->lookup("AC", &obj1)->isName()) { + alternateCaption = new GooString(obj1.getName()); + } 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; +} + +//------------------------------------------------------------------------ +// Annot +//------------------------------------------------------------------------ + +Annot::Annot(XRef *xrefA, Catalog *catalog, Dict *dict) { + ok = gTrue; + xref = xrefA; + appearBuf = NULL; + fontSize = 0; + regen = gTrue; + hasRef = gFalse; + + flags = 0; + initialize(xrefA, catalog, dict); +} + +Annot::Annot(XRef *xrefA, Catalog *catalog, Dict *dict, const Ref& ref) { + ok = gTrue; + xref = xrefA; + appearBuf = NULL; + fontSize = 0; + regen = gTrue; + hasRef = gTrue; + + flags = 0; + this->ref = ref; + initialize(xrefA, catalog, dict); +} + +Annot::Annot(XRef *xrefA, Dict *acroForm, Dict *dict, const Ref& aref, Catalog *catalog) +{ + ok = gTrue; + xref = xrefA; + appearBuf = NULL; + fontSize = 0; + regen = gTrue; + hasRef = gTrue; + + flags = 0; + ref = aref; + initialize (xrefA, acroForm, dict, catalog); + initialize(xrefA, catalog, dict); +} + +Annot::Annot(XRef *xrefA, Dict *acroForm, Dict *dict, Catalog *catalog) { + ok = gTrue; + xref = xrefA; + appearBuf = NULL; + fontSize = 0; + regen = gTrue; + hasRef = gFalse; + + flags = 0; + initialize (xrefA, acroForm, dict, catalog); + initialize(xrefA, catalog, dict); +} + +Annot::~Annot() { + delete rect; + + if (contents) + delete contents; + + if (pageDict) + delete pageDict; + + if(annotName) + delete annotName; + + if (modified) + delete modified; + + appearance.free(); + + if(appearDict) { + delete appearDict; + + if(appearState) + delete appearState; + } + + if (appearBuf) + delete appearBuf; + + if (border) + delete border; + + if (color) + delete color; + + if (optionalContent) + delete optionalContent; +} + +void Annot::initialize(XRef *xrefA, Catalog *catalog, Dict *dict) { + Object obj1; + + // TODO: + // this should be done outside of the Annot class so we can create derivated + // specialized classes. + if (dict->lookup("Subtype", &obj1)->isName()) { + GooString *name = new GooString(obj1.getName()); + + if(!name->cmp("Text")) { + type = typeText; + } else if(!name->cmp("Link")) { + type = typeLink; + } else if(!name->cmp("FreeText")) { + type = typeFreeText; + } else if(!name->cmp("Line")) { + type = typeLine; + } else if(!name->cmp("Square")) { + flags |= flagInvisible; + type = typeSquare; + } else if(!name->cmp("Circle")) { + flags |= flagInvisible; + type = typeCircle; + } else if(!name->cmp("Polygon")) { + flags |= flagInvisible; + type = typePolygon; + } else if(!name->cmp("PolyLine")) { + flags |= flagInvisible; + type = typePolyLine; + } else if(!name->cmp("Highlight")) { + flags |= flagInvisible; + type = typeHighlight; + } else if(!name->cmp("Underline")) { + flags |= flagInvisible; + type = typeUnderline; + } else if(!name->cmp("Squiggly")) { + flags |= flagInvisible; + type = typeSquiggly; + } else if(!name->cmp("StrikeOut")) { + flags |= flagInvisible; + type = typeStrikeOut; + } else if(!name->cmp("Stamp")) { + flags |= flagInvisible; + type = typeStamp; + } else if(!name->cmp("Caret")) { + flags |= flagInvisible; + type = typeCaret; + } else if(!name->cmp("Ink")) { + flags |= flagInvisible; + type = typeInk; + } else if(!name->cmp("Popup")) { + flags |= flagInvisible; + type = typePopup; + } else if(!name->cmp("FileAttachment")) { + flags |= flagInvisible; + type = typeFileAttachment; + } else if(!name->cmp("Sound")) { + flags |= flagInvisible; + type = typeSound; + } else if(!name->cmp("Movie")) { + flags |= flagInvisible; + type = typeMovie; + } else if(!name->cmp("Widget")) { + flags |= flagInvisible; + type = typeWidget; + } else if(!name->cmp("Screen")) { + flags |= flagInvisible; + type = typeScreen; + } else if(!name->cmp("PrinterMark")) { + flags |= flagInvisible; + type = typePrinterMark; + } else if(!name->cmp("TrapNet")) { + flags |= flagInvisible; + type = typeTrapNet; + } else if(!name->cmp("Watermark")) { + flags |= flagInvisible; + type = typeWatermark; + } else if(!name->cmp("3D")) { + flags |= flagInvisible; + type = type3D; + } else { + type = typeUnknown; + error(-1, "Type unknown"); + ok = gFalse; + } + delete name; + } else { + type = typeUnknown; + } + obj1.free(); + + 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(-1, "Bad bounding box for annotation"); + ok = gFalse; + } + obj1.free(); + + if (dict->lookup("Contents", &obj1)->isString()) { + contents = obj1.getString()->copy(); + } else { + contents = NULL; + } + obj1.free(); + + if (dict->lookup("P", &obj1)->isDict()) { + pageDict = obj1.getDict(); + } else { + pageDict = NULL; + } + obj1.free(); + + if (dict->lookup("NM", &obj1)->isString()) { + annotName = obj1.getString()->copy(); + } else { + annotName = NULL; + } + obj1.free(); + + if (dict->lookup("M", &obj1)->isString()) { + modified = obj1.getString()->copy(); + } else { + modified = NULL; + } + obj1.free(); + + if (dict->lookup("F", &obj1)->isInt()) { + flags |= obj1.getInt(); + } else { + flags = flagUnknown; + } + obj1.free(); + + if (dict->lookup("AP", &obj1)->isDict()) { + appearDict = obj1.getDict(); + obj1.free (); + + if (dict->lookup("AS", &obj1)->isName()) { + appearState = new GooString(obj1.getName()); + } else { + appearState = NULL; + } + } else { + appearDict = NULL; + appearState = NULL; + } + obj1.free(); + + if (dict->lookup("BS", &obj1)->isDict()) { + border = new AnnotBorderBS(obj1.getDict()); + } else { + obj1.free(); + + if (dict->lookup("Border", &obj1)->isArray()) + border = new AnnotBorderArray(obj1.getArray()); + else + border = new AnnotBorderArray(); + } + 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(); + + if (dict->lookup("OC", &obj1)->isDict()) { + optionalContent = obj1.getDict(); + } else { + optionalContent = NULL; + } + obj1.free(); +} + +void Annot::initialize(XRef *xrefA, Dict *acroForm, Dict *dict, Catalog *catalog) { + + Object obj1; + + ok = gTrue; + xref = xrefA; + appearBuf = NULL; + fontSize = 0; + + // check if field apperances need to be regenerated + regen = gFalse; + if (acroForm) { + acroForm->lookup("NeedAppearances", &obj1); + if (obj1.isBool() && obj1.getBool()) { + regen = gTrue; + } + obj1.free(); + } + regen = gTrue; +} + +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(); +} + +double Annot::getXMin() { + return rect->x1; +} + +double Annot::getYMin() { + return rect->y1; +} + +/* + * TODO: + */ +void Annot::generateFieldAppearance(Dict *field, Dict *annot, Dict *acroForm) { + Object mkObj, ftObj, appearDict, drObj, obj1, obj2, obj3; + Dict *mkDict; + MemStream *appearStream; + GfxFontDict *fontDict; + GBool hasCaption; + double w, dx, dy, r; + double *dash; + GooString *caption, *da; + GooString **text; + GBool *selection; + int dashLength, ff, quadding, comb, nOptions, topIdx, i, j; + + //do not regenerate appearance if we don't need to + if (!regen) + return; + + // must be a Widget annotation + if (type != typeWidget) { + return; + } + appearBuf = new GooString (); + // get the appearance characteristics (MK) dictionary + if (annot->lookup("MK", &mkObj)->isDict()) { + mkDict = mkObj.getDict(); + } else { + mkDict = NULL; + } + // draw the background + if (mkDict) { + if (mkDict->lookup("BG", &obj1)->isArray() && + obj1.arrayGetLength() > 0) { + setColor(obj1.getArray(), gTrue, 0); + appearBuf->appendf("0 0 {0:.2f} {1:.2f} re f\n", + rect->x2 - rect->x1, rect->y2 - rect->y1); + } + obj1.free(); + } + + // get the field type + fieldLookup(field, "FT", &ftObj); + + // get the field flags (Ff) value + if (fieldLookup(field, "Ff", &obj1)->isInt()) { + ff = obj1.getInt(); + } else { + ff = 0; + } + obj1.free(); + + // draw the border + if (mkDict) { + w = border->getWidth(); + if (w > 0) { + mkDict->lookup("BC", &obj1); + if (!(obj1.isArray() && obj1.arrayGetLength() > 0)) { + mkDict->lookup("BG", &obj1); + } + if (obj1.isArray() && obj1.arrayGetLength() > 0) { + dx = rect->x2 - rect->x1; + dy = rect->y2 - rect->y1; + + // radio buttons with no caption have a round border + hasCaption = mkDict->lookup("CA", &obj2)->isString(); + obj2.free(); + if (ftObj.isName("Btn") && (ff & fieldFlagRadio) && !hasCaption) { + AnnotBorderBS *borderBS = dynamic_cast (border); + r = 0.5 * (dx < dy ? dx : dy); + if (borderBS) { + switch (borderBS->getStyle()) { + case AnnotBorderBS::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"); + // fall through to the solid case + case AnnotBorderBS::borderSolid: + case AnnotBorderBS::borderUnderlined: + appearBuf->appendf("{0:.2f} w\n", w); + setColor(obj1.getArray(), gFalse, 0); + drawCircle(0.5 * dx, 0.5 * dy, r - 0.5 * w, gFalse); + break; + case AnnotBorderBS::borderBeveled: + case AnnotBorderBS::borderInset: + appearBuf->appendf("{0:.2f} w\n", 0.5 * w); + setColor(obj1.getArray(), gFalse, 0); + drawCircle(0.5 * dx, 0.5 * dy, r - 0.25 * w, gFalse); + setColor(obj1.getArray(), gFalse, (borderBS->getStyle() + == AnnotBorderBS::borderBeveled) ? 1 : -1); + drawCircleTopLeft(0.5 * dx, 0.5 * dy, r - 0.75 * w); + setColor(obj1.getArray(), gFalse, (borderBS->getStyle() + == AnnotBorderBS::borderBeveled) ? -1 : 1); + drawCircleBottomRight(0.5 * dx, 0.5 * dy, r - 0.75 * w); + break; + } + } + + } else { + AnnotBorderBS *borderBS = dynamic_cast (border); + if(borderBS) { + switch (borderBS->getStyle()) { + case AnnotBorderBS::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"); + // fall through to the solid case + case AnnotBorderBS::borderSolid: + appearBuf->appendf("{0:.2f} w\n", w); + setColor(obj1.getArray(), gFalse, 0); + appearBuf->appendf("{0:.2f} {0:.2f} {1:.2f} {2:.2f} re s\n", + 0.5 * w, dx - w, dy - w); + break; + case AnnotBorderBS::borderBeveled: + case AnnotBorderBS::borderInset: + setColor(obj1.getArray(), gTrue, (borderBS->getStyle() + == AnnotBorderBS::borderBeveled) ? 1 : -1); + 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"); + setColor(obj1.getArray(), gTrue, (borderBS->getStyle() + == AnnotBorderBS::borderBeveled) ? -1 : 1); + 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 AnnotBorderBS::borderUnderlined: + appearBuf->appendf("{0:.2f} w\n", w); + setColor(obj1.getArray(), gFalse, 0); + 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); + } + } + obj1.free(); + } + } + + // get the resource dictionary + acroForm->lookup("DR", &drObj); + + // build the font dictionary + if (drObj.isDict() && drObj.dictLookup("Font", &obj1)->isDict()) { + fontDict = new GfxFontDict(xref, NULL, obj1.getDict()); + } else { + fontDict = NULL; + } + obj1.free(); + + // get the default appearance string + if (fieldLookup(field, "DA", &obj1)->isNull()) { + obj1.free(); + acroForm->lookup("DA", &obj1); + } + if (obj1.isString()) { + da = obj1.getString()->copy(); + //TODO: look for a font size / name HERE + // => create a function + } else { + da = NULL; + } + obj1.free(); + // draw the field contents if (ftObj.isName("Btn")) { caption = NULL; @@ -524,8 +1055,8 @@ if (mkDict) { if (mkDict->lookup("BC", &obj3)->isArray() && obj3.arrayGetLength() > 0) { - dx = xMax - xMin; - dy = yMax - yMin; + dx = rect->x2 - rect->x1; + dy = rect->y2 - rect->y1; setColor(obj3.getArray(), gTrue, 0); drawCircle(0.5 * dx, 0.5 * dy, 0.2 * (dx < dy ? dx : dy), gTrue); @@ -545,263 +1076,593 @@ } // checkbox } else { - // According to the PDF spec the off state must be named "Off", - // and the on state can be named anything, but Acrobat apparently - // looks for "Yes" and treats anything else as off. - if (fieldLookup(field, "V", &obj1)->isName("Yes")) { - if (!caption) { - caption = new GooString("3"); // ZapfDingbats checkmark - } - drawText(caption, da, fontDict, gFalse, 0, fieldQuadCenter, - gFalse, gTrue); + // According to the PDF spec the off state must be named "Off", + // and the on state can be named anything, but Acrobat apparently + // looks for "Yes" and treats anything else as off. + if (fieldLookup(field, "V", &obj1)->isName("Yes")) { + if (!caption) { + caption = new GooString("3"); // ZapfDingbats checkmark + } + drawText(caption, da, fontDict, gFalse, 0, fieldQuadCenter, + gFalse, gTrue); + } + obj1.free(); + } + if (caption) { + delete caption; + } + } else if (ftObj.isName("Tx")) { + //~ value strings can be Unicode + if (fieldLookup(field, "V", &obj1)->isString()) { + if (fieldLookup(field, "Q", &obj2)->isInt()) { + quadding = obj2.getInt(); + } else { + quadding = fieldQuadLeft; + } + obj2.free(); + comb = 0; + if (ff & fieldFlagComb) { + if (fieldLookup(field, "MaxLen", &obj2)->isInt()) { + comb = obj2.getInt(); + } + obj2.free(); + } + drawText(obj1.getString(), da, fontDict, + ff & fieldFlagMultiline, comb, quadding, gTrue, gFalse); + } + obj1.free(); + } else if (ftObj.isName("Ch")) { + //~ value/option strings can be Unicode + if (fieldLookup(field, "Q", &obj1)->isInt()) { + quadding = obj1.getInt(); + } else { + quadding = fieldQuadLeft; + } + obj1.free(); + // combo box + if (ff & fieldFlagCombo) { + if (fieldLookup(field, "V", &obj1)->isString()) { + drawText(obj1.getString(), da, fontDict, + gFalse, 0, quadding, gTrue, gFalse); + //~ Acrobat draws a popup icon on the right side + } + obj1.free(); + // list box + } else { + if (field->lookup("Opt", &obj1)->isArray()) { + nOptions = obj1.arrayGetLength(); + // get the option text + text = (GooString **)gmallocn(nOptions, sizeof(GooString *)); + for (i = 0; i < nOptions; ++i) { + text[i] = NULL; + obj1.arrayGet(i, &obj2); + if (obj2.isString()) { + text[i] = obj2.getString()->copy(); + } else if (obj2.isArray() && obj2.arrayGetLength() == 2) { + if (obj2.arrayGet(1, &obj3)->isString()) { + text[i] = obj3.getString()->copy(); + } + obj3.free(); + } + obj2.free(); + if (!text[i]) { + text[i] = new GooString(); + } + } + // get the selected option(s) + selection = (GBool *)gmallocn(nOptions, sizeof(GBool)); + //~ need to use the I field in addition to the V field + fieldLookup(field, "V", &obj2); + for (i = 0; i < nOptions; ++i) { + selection[i] = gFalse; + if (obj2.isString()) { + if (!obj2.getString()->cmp(text[i])) { + selection[i] = gTrue; + } + } else if (obj2.isArray()) { + for (j = 0; j < obj2.arrayGetLength(); ++j) { + if (obj2.arrayGet(j, &obj3)->isString() && + !obj3.getString()->cmp(text[i])) { + selection[i] = gTrue; + } + obj3.free(); + } + } + } + obj2.free(); + // get the top index + if (field->lookup("TI", &obj2)->isInt()) { + topIdx = obj2.getInt(); + } else { + topIdx = 0; + } + obj2.free(); + // draw the text + drawListBox(text, selection, nOptions, topIdx, da, fontDict, quadding); + for (i = 0; i < nOptions; ++i) { + delete text[i]; + } + gfree(text); + gfree(selection); + } + obj1.free(); + } + } else if (ftObj.isName("Sig")) { + //~unimp + } else { + error(-1, "Unknown field type"); + } + + if (da) { + delete da; + } + + // 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 + if (drObj.isDict()) { + appearDict.dictAdd(copyString("Resources"), drObj.copy(&obj1)); + } + drObj.free(); + + // build the appearance stream + appearStream = new MemStream(appearBuf->getCString(), 0, + appearBuf->getLength(), &appearDict); + appearance.free(); + appearance.initStream(appearStream); + + if (fontDict) { + delete fontDict; + } + ftObj.free(); + mkObj.free(); +} + + +// Set the current fill or stroke color, based on (which should +// have 1, 3, or 4 elements). If is +1, color is brightened; +// if is -1, color is darkened; otherwise color is not +// modified. +void Annot::setColor(Array *a, GBool fill, int adjust) { + Object obj1; + double color[4]; + int nComps, i; + + nComps = a->getLength(); + if (nComps > 4) { + nComps = 4; + } + for (i = 0; i < nComps && i < 4; ++i) { + if (a->get(i, &obj1)->isNum()) { + color[i] = obj1.getNum(); + } else { + color[i] = 0; + } + obj1.free(); + } + if (nComps == 4) { + adjust = -adjust; + } + if (adjust > 0) { + for (i = 0; i < nComps; ++i) { + color[i] = 0.5 * color[i] + 0.5; + } + } else if (adjust < 0) { + for (i = 0; i < nComps; ++i) { + color[i] = 0.5 * color[i]; + } + } + if (nComps == 4) { + appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:c}\n", + color[0], color[1], color[2], color[3], + fill ? 'k' : 'K'); + } else if (nComps == 3) { + appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:s}\n", + color[0], color[1], color[2], + fill ? "rg" : "RG"); + } else { + appearBuf->appendf("{0:.2f} {1:c}\n", + color[0], + fill ? 'g' : 'G'); + } +} + +void Annot::writeTextString (GooString *text, GooString *appearBuf, int *i, int j, + CharCodeToUnicode *ccToUnicode) +{ + CharCode c; + int charSize; + + if (text->hasUnicodeMarker()) { + //we need to have an even number of chars + if ((j-*i)%2 != 0) { + error(-1, "Annot::writeTextString, bad unicode string"); + return; + } + //skip unicode marker an go one char forward because we read character by pairs + (*i) += 3; + charSize = 2; + } else + charSize = 1; + + for (; (*i) < j; (*i)+=charSize) { + c = text->getChar(*i); + if (ccToUnicode && text->hasUnicodeMarker()) { + char ctmp[2]; + ctmp[0] = text->getChar((*i)-1); + ctmp[1] = text->getChar((*i)); + ccToUnicode->mapToCharCode((Unicode*)ctmp, &c, 2); + if (c == '(' || c == ')' || c == '\\') + appearBuf->append('\\'); + appearBuf->append(c); + } else { + c &= 0xff; + if (c == '(' || c == ')' || c == '\\') { + appearBuf->append('\\'); + appearBuf->append(c); + } else if (c < 0x20 || c >= 0x80) { + appearBuf->appendf("\\{0:03o}", c); + } else { + appearBuf->append(c); } - obj1.free(); - } - if (caption) { - delete caption; } - } else if (ftObj.isName("Tx")) { - //~ value strings can be Unicode - if (fieldLookup(field, "V", &obj1)->isString()) { - if (fieldLookup(field, "Q", &obj2)->isInt()) { - quadding = obj2.getInt(); - } else { - quadding = fieldQuadLeft; + } +} + +// Draw the variable text or caption for a field. +void Annot::drawText(GooString *text, GooString *da, GfxFontDict *fontDict, + GBool multiline, int comb, int quadding, + GBool txField, GBool forceZapfDingbats) { + GooList *daToks; + GooString *tok; + GfxFont *font; + double fontSize, fontSize2, borderWidth, x, xPrev, y, w, w2, wMax; + int tfPos, tmPos, i, j, k, c; + + //~ 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; } - obj2.free(); - comb = 0; - if (ff & fieldFlagComb) { - if (fieldLookup(field, "MaxLen", &obj2)->isInt()) { - comb = obj2.getInt(); - } - obj2.free(); + 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; } - drawText(obj1.getString(), da, fontDict, - ff & fieldFlagMultiline, comb, quadding, gTrue, gFalse); } - obj1.free(); - } else if (ftObj.isName("Ch")) { - //~ value/option strings can be Unicode - if (fieldLookup(field, "Q", &obj1)->isInt()) { - quadding = obj1.getInt(); - } else { - quadding = fieldQuadLeft; + 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; + } } - obj1.free(); - // combo box - if (ff & fieldFlagCombo) { - if (fieldLookup(field, "V", &obj1)->isString()) { - drawText(obj1.getString(), da, fontDict, - gFalse, 0, quadding, gTrue, gFalse); - //~ Acrobat draws a popup icon on the right side + } else { + daToks = NULL; + } + + // force ZapfDingbats + //~ this should create the font if needed (?) + 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 (!fontDict || !(font = fontDict->lookup(tok->getCString() + 1))) { + error(-1, "Unknown font in field's DA string"); } - obj1.free(); - // list box } else { - if (field->lookup("Opt", &obj1)->isArray()) { - nOptions = obj1.arrayGetLength(); - // get the option text - text = (GooString **)gmallocn(nOptions, sizeof(GooString *)); - for (i = 0; i < nOptions; ++i) { - text[i] = NULL; - obj1.arrayGet(i, &obj2); - if (obj2.isString()) { - text[i] = obj2.getString()->copy(); - } else if (obj2.isArray() && obj2.arrayGetLength() == 2) { - if (obj2.arrayGet(1, &obj3)->isString()) { - text[i] = obj3.getString()->copy(); - } - obj3.free(); - } - obj2.free(); - if (!text[i]) { - text[i] = new GooString(); - } - } - // get the selected option(s) - selection = (GBool *)gmallocn(nOptions, sizeof(GBool)); - //~ need to use the I field in addition to the V field - fieldLookup(field, "V", &obj2); - for (i = 0; i < nOptions; ++i) { - selection[i] = gFalse; - if (obj2.isString()) { - if (!obj2.getString()->cmp(text[i])) { - selection[i] = gTrue; - } - } else if (obj2.isArray()) { - for (j = 0; j < obj2.arrayGetLength(); ++j) { - if (obj2.arrayGet(j, &obj3)->isString() && - !obj3.getString()->cmp(text[i])) { - selection[i] = gTrue; - } - obj3.free(); - } + error(-1, "Invalid font name in 'Tf' operator in field's DA string"); + } + tok = (GooString *)daToks->get(tfPos + 1); + fontSize = atof(tok->getCString()); + } else { + error(-1, "Missing 'Tf' operator in field's DA string"); + } + if (!font) { + return; + } + + // get the border width + borderWidth = border->getWidth(); + + // setup + if (txField) { + appearBuf->append("/Tx BMC\n"); + } + appearBuf->append("q\n"); + appearBuf->append("BT\n"); + // multi-line text + if (multiline) { + // note: the comb flag is ignored in multiline mode + + wMax = rect->x2 - rect->x1 - 2 * borderWidth - 4; + + // compute font autosize + if (fontSize == 0) { + for (fontSize = 20; fontSize > 1; --fontSize) { + y = rect->y2 - rect->y1; + w2 = 0; + i = 0; + while (i < text->getLength()) { + getNextLine(text, i, font, fontSize, wMax, &j, &w, &k); + if (w > w2) { + w2 = w; } + i = k; + y -= fontSize; } - obj2.free(); - // get the top index - if (field->lookup("TI", &obj2)->isInt()) { - topIdx = obj2.getInt(); - } else { - topIdx = 0; - } - obj2.free(); - // draw the text - drawListBox(text, selection, nOptions, topIdx, da, fontDict, quadding); - for (i = 0; i < nOptions; ++i) { - delete text[i]; + // approximate the descender for the last line + if (y >= 0.33 * fontSize) { + break; } - gfree(text); - gfree(selection); } - obj1.free(); + 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 = rect->y2 - rect->y1; + + // 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()) { + + getNextLine(text, i, font, fontSize, wMax, &j, &w, &k); + + // compute text start position + switch (quadding) { + case fieldQuadLeft: + default: + x = borderWidth + 2; + break; + case fieldQuadCenter: + x = (rect->x2 - rect->x1 - w) / 2; + break; + case fieldQuadRight: + x = rect->x2 - rect->x1 - borderWidth - 2 - w; + break; + } + + // draw the line + appearBuf->appendf("{0:.2f} {1:.2f} Td\n", x - xPrev, -fontSize); + appearBuf->append('('); + writeTextString (text, appearBuf, &i, j, font->getToUnicode()); + appearBuf->append(") Tj\n"); + + // next line + i = k; + xPrev = x; } - } else if (ftObj.isName("Sig")) { - //~unimp - } else { - error(-1, "Unknown field type"); - } - if (da) { - delete da; - } + // single-line text + } else { + //~ replace newlines with spaces? - what does Acrobat do? - // 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(xMax - xMin)); - obj1.arrayAdd(obj2.initReal(yMax - yMin)); - appearDict.dictAdd(copyString("BBox"), &obj1); + // comb formatting + if (comb > 0) { + // compute comb spacing + w = (rect->x2 - rect->x1 - 2 * borderWidth) / comb; - // set the resource dictionary - if (drObj.isDict()) { - appearDict.dictAdd(copyString("Resources"), drObj.copy(&obj1)); - } - drObj.free(); + // compute font autosize + if (fontSize == 0) { + fontSize = rect->y2 - rect->y1 - 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); + } + } - // build the appearance stream - appearStream = new MemStream(appearBuf->getCString(), 0, - appearBuf->getLength(), &appearDict); - appearance.free(); - appearance.initStream(appearStream); + // compute text start position + switch (quadding) { + case fieldQuadLeft: + default: + x = borderWidth + 2; + break; + case fieldQuadCenter: + x = borderWidth + 2 + 0.5 * (comb - text->getLength()) * w; + break; + case fieldQuadRight: + x = borderWidth + 2 + (comb - text->getLength()) * w; + break; + } + y = 0.5 * (rect->y2 - rect->y1) - 0.4 * fontSize; - if (fontDict) { - delete fontDict; - } - ftObj.free(); - mkObj.free(); -} + // 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(' '); + } + } -// Set the current fill or stroke color, based on (which should -// have 1, 3, or 4 elements). If is +1, color is brightened; -// if is -1, color is darkened; otherwise color is not -// modified. -void Annot::setColor(Array *a, GBool fill, int adjust) { - Object obj1; - double color[4]; - int nComps, i; + // 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 + //~ this should center (instead of left-justify) each character within + //~ its comb cell + for (i = 0; i < text->getLength(); ++i) { + if (i > 0) { + appearBuf->appendf("{0:.2f} 0 Td\n", w); + } + appearBuf->append('('); + //~ it would be better to call it only once for the whole string instead of once for + //each character => but we need to handle centering in writeTextString + writeTextString (text, appearBuf, &i, i+1, font->getToUnicode()); + appearBuf->append(") Tj\n"); + } - nComps = a->getLength(); - if (nComps > 4) { - nComps = 4; - } - for (i = 0; i < nComps && i < 4; ++i) { - if (a->get(i, &obj1)->isNum()) { - color[i] = obj1.getNum(); + // regular (non-comb) formatting } else { - color[i] = 0; - } - obj1.free(); - } - if (nComps == 4) { - adjust = -adjust; - } - if (adjust > 0) { - for (i = 0; i < nComps; ++i) { - color[i] = 0.5 * color[i] + 0.5; - } - } else if (adjust < 0) { - for (i = 0; i < nComps; ++i) { - color[i] = 0.5 * color[i]; - } - } - if (nComps == 4) { - appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:c}\n", - color[0], color[1], color[2], color[3], - fill ? 'k' : 'K'); - } else if (nComps == 3) { - appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:s}\n", - color[0], color[1], color[2], - fill ? "rg" : "RG"); - } else { - appearBuf->appendf("{0:.2f} {1:c}\n", - color[0], - fill ? 'g' : 'G'); - } -} + // compute string width + if (font && !font->isCIDFont()) { + w = 0; + for (i = 0; i < text->getLength(); ++i) { + w += ((Gfx8BitFont *)font)->getWidth(text->getChar(i)); + } + } else { + // otherwise, make a crude estimate + w = text->getLength() * 0.5; + } -void Annot::writeTextString (GooString *text, GooString *appearBuf, int *i, int j, - CharCodeToUnicode *ccToUnicode) -{ - CharCode c; - int charSize; + // compute font autosize + if (fontSize == 0) { + fontSize = rect->y2 - rect->y1 - 2 * borderWidth; + fontSize2 = (rect->x2 - rect->x1 - 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); + } + } - if (text->hasUnicodeMarker()) { - //we need to have an even number of chars - if ((j-*i)%2 != 0) { - error(-1, "Annot::writeTextString, bad unicode string"); - return; - } - //skip unicode marker an go one char forward because we read character by pairs - (*i) += 3; - charSize = 2; - } else - charSize = 1; + // compute text start position + w *= fontSize; + switch (quadding) { + case fieldQuadLeft: + default: + x = borderWidth + 2; + break; + case fieldQuadCenter: + x = (rect->x2 - rect->x1 - w) / 2; + break; + case fieldQuadRight: + x = rect->x2 - rect->x1 - borderWidth - 2 - w; + break; + } + y = 0.5 * (rect->y2 - rect->y1) - 0.4 * fontSize; - for (; (*i) < j; (*i)+=charSize) { - c = text->getChar(*i); - if (ccToUnicode && text->hasUnicodeMarker()) { - char ctmp[2]; - ctmp[0] = text->getChar((*i)-1); - ctmp[1] = text->getChar((*i)); - ccToUnicode->mapToCharCode((Unicode*)ctmp, &c, 2); - if (c == '(' || c == ')' || c == '\\') - appearBuf->append('\\'); - appearBuf->append(c); - } else { - c &= 0xff; - if (c == '(' || c == ')' || c == '\\') { - appearBuf->append('\\'); - appearBuf->append(c); - } else if (c < 0x20 || c >= 0x80) { - appearBuf->appendf("\\{0:03o}", c); - } else { - appearBuf->append(c); + // 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 + appearBuf->append('('); + i=0; + writeTextString (text, appearBuf, &i, text->getLength(), font->getToUnicode()); + appearBuf->append(") Tj\n"); } } + // cleanup + appearBuf->append("ET\n"); + appearBuf->append("Q\n"); + if (txField) { + appearBuf->append("EMC\n"); + } + if (daToks) { + deleteGooList(daToks, GooString); + } } -// Draw the variable text or caption for a field. -void Annot::drawText(GooString *text, GooString *da, GfxFontDict *fontDict, - GBool multiline, int comb, int quadding, - GBool txField, GBool forceZapfDingbats) { +// Draw the variable text or caption for a field. +void Annot::drawListBox(GooString **text, GBool *selection, + int nOptions, int topIdx, + GooString *da, GfxFontDict *fontDict, GBool quadding) { GooList *daToks; GooString *tok; GfxFont *font; - double fontSize, fontSize2, border, x, xPrev, y, w, w2, wMax; - int tfPos, tmPos, i, j, k, c; + double fontSize, fontSize2, borderWidth, x, y, w, wMax; + int tfPos, tmPos, i, j, c; //~ 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) { @@ -809,38 +1670,27 @@ i = 0; while (i < da->getLength()) { while (i < da->getLength() && Lexer::isSpace(da->getChar(i))) { - ++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 (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; + tfPos = i - 2; } else if (i >= 6 && !((GooString *)daToks->get(i))->cmp("Tm")) { - tmPos = i - 6; + tmPos = i - 6; } } } else { daToks = NULL; } - // force ZapfDingbats - //~ this should create the font if needed (?) - 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; @@ -863,56 +1713,87 @@ } // get the border width - border = borderStyle->getWidth(); + borderWidth = border->getWidth(); - // setup - if (txField) { - appearBuf->append("/Tx BMC\n"); + // compute font autosize + if (fontSize == 0) { + wMax = 0; + for (i = 0; i < nOptions; ++i) { + if (font && !font->isCIDFont()) { + w = 0; + for (j = 0; j < text[i]->getLength(); ++j) { + w += ((Gfx8BitFont *)font)->getWidth(text[i]->getChar(j)); + } + } else { + // otherwise, make a crude estimate + w = text[i]->getLength() * 0.5; + } + 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); + } } - appearBuf->append("q\n"); - appearBuf->append("BT\n"); - // multi-line text - if (multiline) { - // note: the comb flag is ignored in multiline mode + // draw the text + y = rect->y2 - rect->y1 - 1.1 * fontSize; + for (i = topIdx; i < nOptions; ++i) { + // setup + appearBuf->append("q\n"); - wMax = xMax - xMin - 2 * border - 4; + // draw the background if selected + if (selection[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); + } - // compute font autosize - if (fontSize == 0) { - for (fontSize = 20; fontSize > 1; --fontSize) { - y = yMax - yMin; - w2 = 0; - i = 0; - while (i < text->getLength()) { - getNextLine(text, i, font, fontSize, wMax, &j, &w, &k); - if (w > w2) { - w2 = w; - } - i = k; - 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); + // setup + appearBuf->append("BT\n"); + + // compute string width + if (font && !font->isCIDFont()) { + w = 0; + for (j = 0; j < text[i]->getLength(); ++j) { + w += ((Gfx8BitFont *)font)->getWidth(text[i]->getChar(j)); } + } else { + // otherwise, make a crude estimate + w = text[i]->getLength() * 0.5; } - // starting y coordinate - // (note: each line of text starts with a Td operator that moves - // down a line) - y = yMax - yMin; + // compute text start position + w *= fontSize; + switch (quadding) { + case fieldQuadLeft: + default: + x = borderWidth + 2; + break; + case fieldQuadCenter: + x = (rect->x2 - rect->x1 - w) / 2; + break; + case fieldQuadRight: + 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->append('0'); + tok->appendf("{0:.2f}", x); tok = (GooString *)daToks->get(tmPos + 5); tok->clear(); tok->appendf("{0:.2f}", y); @@ -920,611 +1801,999 @@ // write the DA string if (daToks) { - for (i = 0; i < daToks->getLength(); ++i) { - appearBuf->append((GooString *)daToks->get(i))->append(' '); + 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 {0:.2f} Tm\n", y); + appearBuf->appendf("1 0 0 1 {0:.2f} {1:.2f} Tm\n", x, y); } - // write a series of lines of text - i = 0; - xPrev = 0; - while (i < text->getLength()) { + // change the text color if selected + if (selection[i]) { + appearBuf->append("1 g\n"); + } + + // write the text string + appearBuf->append('('); + j = 0; + writeTextString (text[i], appearBuf, &j, text[i]->getLength(), font->getToUnicode()); + appearBuf->append(") Tj\n"); + + // cleanup + appearBuf->append("ET\n"); + appearBuf->append("Q\n"); + + // next line + y -= 1.1 * fontSize; + } + + if (daToks) { + deleteGooList(daToks, GooString); + } +} + +// Figure out how much text will fit on the next line. Returns: +// *end = one past the last character to be included +// *width = width of the characters start .. end-1 +// *next = index of first character on the following line +void Annot::getNextLine(GooString *text, int start, + GfxFont *font, double fontSize, double wMax, + int *end, double *width, int *next) { + double w, dw; + int j, k, c; + + // figure out how much text will fit on the line + //~ what does Adobe do with tabs? + w = 0; + for (j = start; j < text->getLength() && w <= wMax; ++j) { + c = text->getChar(j) & 0xff; + if (c == 0x0a || c == 0x0d) { + break; + } + if (font && !font->isCIDFont()) { + dw = ((Gfx8BitFont *)font)->getWidth(c) * fontSize; + } else { + // otherwise, make a crude estimate + dw = 0.5 * fontSize; + } + w += dw; + } + if (w > wMax) { + for (k = j; k > start && text->getChar(k-1) != ' '; --k) ; + for (; k > start && text->getChar(k-1) == ' '; --k) ; + if (k > start) { + j = k; + } + if (j == start) { + // handle the pathological case where the first character is + // too wide to fit on the line all by itself + j = start + 1; + } + } + *end = j; + + // compute the width + w = 0; + for (k = start; k < j; ++k) { + if (font && !font->isCIDFont()) { + dw = ((Gfx8BitFont *)font)->getWidth(text->getChar(k)) * fontSize; + } else { + // otherwise, make a crude estimate + dw = 0.5 * fontSize; + } + w += dw; + } + *width = w; + + // next line + while (j < text->getLength() && text->getChar(j) == ' ') { + ++j; + } + if (j < text->getLength() && text->getChar(j) == 0x0d) { + ++j; + } + if (j < text->getLength() && text->getChar(j) == 0x0a) { + ++j; + } + *next = j; +} + +// Draw an (approximate) circle of radius centered at (, ). +// If 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 +// centered at (, ). +void Annot::drawCircleTopLeft(double cx, double cy, double r) { + double r2; - getNextLine(text, i, font, fontSize, wMax, &j, &w, &k); + 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"); +} - // compute text start position - switch (quadding) { - case fieldQuadLeft: - default: - x = border + 2; - break; - case fieldQuadCenter: - x = (xMax - xMin - w) / 2; - break; - case fieldQuadRight: - x = xMax - xMin - border - 2 - w; - break; - } +// Draw the bottom-right half of an (approximate) circle of radius +// centered at (, ). +void Annot::drawCircleBottomRight(double cx, double cy, double r) { + double r2; - // draw the line - appearBuf->appendf("{0:.2f} {1:.2f} Td\n", x - xPrev, -fontSize); - appearBuf->append('('); - writeTextString (text, appearBuf, &i, j, font->getToUnicode()); - appearBuf->append(") Tj\n"); + 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"); +} - // next line - i = k; - xPrev = x; - } +// Look up an inheritable field dictionary entry. +Object *Annot::fieldLookup(Dict *field, char *key, Object *obj) { + Dict *dict; + Object parent; - // single-line text + dict = field; + if (!dict->lookup(key, obj)->isNull()) { + return obj; + } + obj->free(); + if (dict->lookup("Parent", &parent)->isDict()) { + fieldLookup(parent.getDict(), key, obj); } else { - //~ replace newlines with spaces? - what does Acrobat do? + obj->initNull(); + } + parent.free(); + return obj; +} - // comb formatting - if (comb > 0) { - // compute comb spacing - w = (xMax - xMin - 2 * border) / comb; +void Annot::draw(Gfx *gfx, GBool printing) { + Object obj; + GBool isLink; - // compute font autosize - if (fontSize == 0) { - fontSize = yMax - yMin - 2 * border; - if (w < fontSize) { - fontSize = w; - } - fontSize = floor(fontSize); - if (tfPos >= 0) { - tok = (GooString *)daToks->get(tfPos + 1); - tok->clear(); - tok->appendf("{0:.2f}", fontSize); - } - } + // check the flags + if ((flags & flagHidden) || + (printing && !(flags & flagPrint)) || + (!printing && (flags & flagNoView))) { + return; + } - // compute text start position - switch (quadding) { - case fieldQuadLeft: - default: - x = border + 2; - break; - case fieldQuadCenter: - x = border + 2 + 0.5 * (comb - text->getLength()) * w; - break; - case fieldQuadRight: - x = border + 2 + (comb - text->getLength()) * w; - break; - } - y = 0.5 * (yMax - yMin) - 0.4 * fontSize; + // draw the appearance stream + isLink = (type == typeLink); + appearance.fetch(xref, &obj); + gfx->drawAnnot(&obj, isLink ? border : (AnnotBorder *)NULL, color, + rect->x1, rect->y1, rect->x2, rect->y2); + obj.free(); +} - // 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); - } +//------------------------------------------------------------------------ +// AnnotMarkup +//------------------------------------------------------------------------ + +AnnotMarkup::AnnotMarkup(XRef *xrefA, Catalog *catalog, Dict *dict) { + initialize(xrefA, catalog, dict); +} - // write the DA string - if (daToks) { - for (i = 0; i < daToks->getLength(); ++i) { - appearBuf->append((GooString *)daToks->get(i))->append(' '); - } - } +AnnotMarkup::~AnnotMarkup() { + if (label) + delete label; + + if (popup) + delete popup; + + if (date) + delete date; + + if (inReplyTo) + delete inReplyTo; + + if (subject) + delete subject; +} - // 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 - //~ this should center (instead of left-justify) each character within - //~ its comb cell - for (i = 0; i < text->getLength(); ++i) { - if (i > 0) { - appearBuf->appendf("{0:.2f} 0 Td\n", w); - } - appearBuf->append('('); - //~ it would be better to call it only once for the whole string instead of once for - //each character => but we need to handle centering in writeTextString - writeTextString (text, appearBuf, &i, i+1, font->getToUnicode()); - appearBuf->append(") Tj\n"); - } +void AnnotMarkup::initialize(XRef *xrefA, Catalog *catalog, Dict *dict) { + Object obj1; + + if (dict->lookup("T", &obj1)->isString()) { + label = obj1.getString()->copy(); + } else { + label = NULL; + } + obj1.free(); + + if (dict->lookup("Popup", &obj1)->isDict()) { + popup = obj1.getDict(); + } 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->lookup("IRT", &obj1)->isDict()) { + inReplyTo = obj1.getDict(); + } else { + inReplyTo = NULL; + } + obj1.free(); + + if (dict->lookup("Subj", &obj1)->isString()) { + subject = obj1.getString()->copy(); + } else { + subject = NULL; + } + obj1.free(); + + if (dict->lookup("RT", &obj1)->isName()) { + GooString *name = new GooString(obj1.getName()); - // regular (non-comb) formatting + if(!name->cmp("R")) { + replyTo = replyTypeR; + } else if(!name->cmp("Group")) { + replyTo = replyTypeGroup; } else { - // compute string width - if (font && !font->isCIDFont()) { - w = 0; - for (i = 0; i < text->getLength(); ++i) { - w += ((Gfx8BitFont *)font)->getWidth(text->getChar(i)); - } - } else { - // otherwise, make a crude estimate - w = text->getLength() * 0.5; - } - - // compute font autosize - if (fontSize == 0) { - fontSize = yMax - yMin - 2 * border; - fontSize2 = (xMax - xMin - 4 - 2 * border) / 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 fieldQuadLeft: - default: - x = border + 2; - break; - case fieldQuadCenter: - x = (xMax - xMin - w) / 2; - break; - case fieldQuadRight: - x = xMax - xMin - border - 2 - w; - break; - } - y = 0.5 * (yMax - yMin) - 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 - appearBuf->append('('); - i=0; - writeTextString (text, appearBuf, &i, text->getLength(), font->getToUnicode()); - appearBuf->append(") Tj\n"); + replyTo = replyTypeR; } + delete name; + } else { + replyTo = replyTypeR; } - // cleanup - appearBuf->append("ET\n"); - appearBuf->append("Q\n"); - if (txField) { - appearBuf->append("EMC\n"); - } - if (daToks) { - deleteGooList(daToks, GooString); + obj1.free(); + + if (dict->lookup("ExData", &obj1)->isDict()) { + exData = parseAnnotExternalData(obj1.getDict()); + } else { + exData = annotExternalDataMarkupUnknown; } + obj1.free(); } -// Draw the variable text or caption for a field. -void Annot::drawListBox(GooString **text, GBool *selection, - int nOptions, int topIdx, - GooString *da, GfxFontDict *fontDict, GBool quadding) { - GooList *daToks; - GooString *tok; - GfxFont *font; - double fontSize, fontSize2, border, x, y, w, wMax; - int tfPos, tmPos, i, j, c; +//------------------------------------------------------------------------ +// AnnotText +//------------------------------------------------------------------------ - //~ 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) +AnnotText::AnnotText(XRef *xrefA, Catalog *catalog, Dict *dict) : + Annot(xref, catalog, dict), AnnotMarkup(xref, catalog, dict) { + + flags = flagNoZoom & flagNoRotate; + initialize(xrefA, catalog, dict); +} - // 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; +void AnnotText::setModified(GooString* date) { + if(date) { + delete modified; + modified = new GooString(date); + } +} + +void AnnotText::initialize(XRef *xrefA, Catalog *catalog, Dict *dict) { + Object obj1; + + if (dict->lookup("Open", &obj1)->isBool()) + open = obj1.getBool(); + else + open = gFalse; + obj1.free(); + + if (dict->lookup("Name", &obj1)->isName()) { + GooString *name = new GooString(obj1.getName()); + + if(!name->cmp("Comment")) { + icon = iconComment; + } else if(!name->cmp("Key")) { + icon = iconKey; + } else if(!name->cmp("Help")) { + icon = iconHelp; + } else if(!name->cmp("NewParagraph")) { + icon = iconNewParagraph; + } else if(!name->cmp("Paragraph")) { + icon = iconParagraph; + } else if(!name->cmp("Insert")) { + icon = iconInsert; + } else { + icon = iconNote; + } + delete name; + } else { + icon = iconNote; + } + obj1.free(); + + if (dict->lookup("StateModel", &obj1)->isString()) { + Object obj2; + GooString *name = 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; } - 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; + obj2.free(); + + if(!name->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(!name->cmp("Review")) { + switch (state) { + case stateUnknown: + state = stateNone; + break; + case stateMarked: + case stateUnmarked: + state = stateUnknown; + break; + default: + break; + } + } else { + state = stateUnknown; } } else { - daToks = NULL; + state = stateUnknown; + } + obj1.free(); +} + +//------------------------------------------------------------------------ +// AnnotLink +//------------------------------------------------------------------------ + +AnnotLink::AnnotLink(XRef *xrefA, Catalog *catalog, Dict *dict) : + Annot(xref, catalog, dict) { + initialize(xrefA, catalog, dict); +} + +AnnotLink::~AnnotLink() { + if (actionDict) + delete actionDict; + + if (uriAction) + delete uriAction; + + if(quadrilaterals) { + for(int i = 0; i < quadrilateralsLength; i++) + delete quadrilaterals[i]; + + gfree (quadrilaterals); } +} - // 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 (!fontDict || !(font = fontDict->lookup(tok->getCString() + 1))) { - error(-1, "Unknown font in field's DA string"); - } +void AnnotLink::initialize(XRef *xrefA, Catalog *catalog, Dict *dict) { + Object obj1; + + if (dict->lookup("A", &obj1)->isDict()) { + actionDict = obj1.getDict(); + } else { + actionDict = NULL; + } + obj1.free(); + + if (dict->lookup("H", &obj1)->isName()) { + GooString *effect = new GooString(obj1.getName()); + + if (!effect->cmp("N")) { + linkEffect = effectNone; + } else if (!effect->cmp("I")) { + linkEffect = effectInvert; + } else if (!effect->cmp("O")) { + linkEffect = effectOutline; + } else if (!effect->cmp("P")) { + linkEffect = effectPush; } else { - error(-1, "Invalid font name in 'Tf' operator in field's DA string"); + linkEffect = effectInvert; } - tok = (GooString *)daToks->get(tfPos + 1); - fontSize = atof(tok->getCString()); + delete effect; } else { - error(-1, "Missing 'Tf' operator in field's DA string"); + linkEffect = effectInvert; } - if (!font) { - return; + obj1.free(); + + if (dict->lookup("PA", &obj1)->isDict()) { + uriAction = obj1.getDict(); + } else { + uriAction = NULL; } + obj1.free(); + + /* + * TODO: + * QuadPoints should be ignored if any coordinate in the array lies outside + * the region specified by Rect. + */ + if(dict->lookup("QuadPoints", &obj1)->isArray()) { + quadrilateralsLength = obj1.arrayGetLength(); + if((quadrilateralsLength % 8) == 0) { + Object obj2; - // get the border width - border = borderStyle->getWidth(); - - // compute font autosize - if (fontSize == 0) { - wMax = 0; - for (i = 0; i < nOptions; ++i) { - if (font && !font->isCIDFont()) { - w = 0; - for (j = 0; j < text[i]->getLength(); ++j) { - w += ((Gfx8BitFont *)font)->getWidth(text[i]->getChar(j)); - } - } else { - // otherwise, make a crude estimate - w = text[i]->getLength() * 0.5; - } - if (w > wMax) { - wMax = w; + quadrilaterals = (AnnotQuadPoints **) gmallocn + ((quadrilateralsLength / 8), sizeof(AnnotQuadPoints *)); + for(int i = 0; i < quadrilateralsLength; i += 8) { + double x1, y1, x2, y2, x3, y3, x4, y4; + + (obj1.arrayGet(i, &obj2)->isNum() ? x1 = obj2.getNum() : x1 = 0); + obj2.free(); + (obj1.arrayGet((i + 1), &obj2)->isNum() ? y1 = obj2.getNum() : y1 = 0); + obj2.free(); + (obj1.arrayGet((i + 2), &obj2)->isNum() ? x2 = obj2.getNum() : x2 = 0); + obj2.free(); + (obj1.arrayGet((i + 3), &obj2)->isNum() ? y2 = obj2.getNum() : y2 = 0); + obj2.free(); + (obj1.arrayGet((i + 4), &obj2)->isNum() ? x3 = obj2.getNum() : x3 = 0); + obj2.free(); + (obj1.arrayGet((i + 5), &obj2)->isNum() ? y3 = obj2.getNum() : y3 = 0); + obj2.free(); + (obj1.arrayGet((i + 6), &obj2)->isNum() ? x4 = obj2.getNum() : x4 = 0); + obj2.free(); + (obj1.arrayGet((i + 7), &obj2)->isNum() ? y4 = obj2.getNum() : y4 = 0); + obj2.free(); + + quadrilaterals[i / 8] = + new AnnotQuadPoints(x1, y1, x2, y2, x3, y3, x4, y4); } + quadrilateralsLength /= 8; + } else { + quadrilaterals = NULL; + quadrilateralsLength = 0; } - fontSize = yMax - yMin - 2 * border; - fontSize2 = (xMax - xMin - 4 - 2 * border) / 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); - } + } else { + quadrilaterals = NULL; } - // draw the text - y = yMax - yMin - 1.1 * fontSize; - for (i = topIdx; i < nOptions; ++i) { - // setup - appearBuf->append("q\n"); + obj1.free(); +} - // draw the background if selected - if (selection[i]) { - appearBuf->append("0 g f\n"); - appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} re f\n", - border, - y - 0.2 * fontSize, - xMax - xMin - 2 * border, - 1.1 * fontSize); - } +//------------------------------------------------------------------------ +// AnnotFreeText +//------------------------------------------------------------------------ - // setup - appearBuf->append("BT\n"); +AnnotFreeText::AnnotFreeText(XRef *xrefA, Catalog *catalog, Dict *dict) : + Annot(xref, catalog, dict), AnnotMarkup(xref, catalog, dict) { + initialize(xrefA, catalog, dict); +} - // compute string width - if (font && !font->isCIDFont()) { - w = 0; - for (j = 0; j < text[i]->getLength(); ++j) { - w += ((Gfx8BitFont *)font)->getWidth(text[i]->getChar(j)); - } +AnnotFreeText::~AnnotFreeText() { + delete appearanceString; + + if (styleString) + delete styleString; + + if (calloutLine) + delete calloutLine; + + if (borderEffect) + delete borderEffect; + + if (rectangle) + delete rectangle; +} + +void AnnotFreeText::initialize(XRef *xrefA, Catalog *catalog, Dict *dict) { + Object obj1; + + if (dict->lookup("DA", &obj1)->isString()) { + appearanceString = obj1.getString()->copy(); + } else { + appearanceString = new GooString(); + error(-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 { - // otherwise, make a crude estimate - w = text[i]->getLength() * 0.5; + calloutLine = new AnnotCalloutLine(x1, y1, x2, y2); } + } else { + calloutLine = NULL; + } + obj1.free(); + + if (dict->lookup("IT", &obj1)->isName()) { + GooString *name = new GooString(obj1.getName()); - // compute text start position - w *= fontSize; - switch (quadding) { - case fieldQuadLeft: - default: - x = border + 2; - break; - case fieldQuadCenter: - x = (xMax - xMin - w) / 2; - break; - case fieldQuadRight: - x = xMax - xMin - border - 2 - w; - break; + if (!name->cmp("FreeTextCallout")) { + intent = intentFreeTextCallout; + } else if (!name->cmp("FreeTextTypeWriter")) { + intent = intentFreeTextTypeWriter; + } else { + intent = intentFreeTextCallout; } + delete name; + } else { + intent = intentFreeTextCallout; + } + obj1.free(); + + if (dict->lookup("BE", &obj1)->isDict()) { + borderEffect = new AnnotBorderEffect(obj1.getDict()); + } else { + borderEffect = NULL; + } + obj1.free(); - // 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); - } + if (dict->lookup("RD", &obj1)->isArray() && obj1.arrayGetLength() == 4) { + Object obj2; + rectangle = new PDFRectangle(); - // write the DA string - if (daToks) { - for (j = 0; j < daToks->getLength(); ++j) { - appearBuf->append((GooString *)daToks->get(j))->append(' '); - } - } + (obj1.arrayGet(0, &obj2)->isNum() ? rectangle->x1 = obj2.getNum() : + rectangle->x1 = 0); + obj2.free(); + (obj1.arrayGet(1, &obj2)->isNum() ? rectangle->y1 = obj2.getNum() : + rectangle->y1 = 0); + obj2.free(); + (obj1.arrayGet(2, &obj2)->isNum() ? rectangle->x2 = obj2.getNum() : + rectangle->x2 = 1); + obj2.free(); + (obj1.arrayGet(3, &obj2)->isNum() ? rectangle->y2 = obj2.getNum() : + rectangle->y2 = 1); + obj2.free(); - // 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); - } + if (rectangle->x1 > rectangle->x2) { + double t = rectangle->x1; + rectangle->x1 = rectangle->x2; + rectangle->x2 = t; + } + if (rectangle->y1 > rectangle->y2) { + double t = rectangle->y1; + rectangle->y1 = rectangle->y2; + rectangle->y2 = t; + } + + if ((rectangle->x1 + rectangle->x2) > (rect->x2 - rect->x1)) + rectangle->x1 = rectangle->x2 = 0; + + if ((rectangle->y1 + rectangle->y2) > (rect->y2 - rect->y1)) + rectangle->y1 = rectangle->y2 = 0; + } else { + rectangle = NULL; + } + 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(); +} - // change the text color if selected - if (selection[i]) { - appearBuf->append("1 g\n"); - } +//------------------------------------------------------------------------ +// AnnotLine +//------------------------------------------------------------------------ - // write the text string - appearBuf->append('('); - j = 0; - writeTextString (text[i], appearBuf, &j, text[i]->getLength(), font->getToUnicode()); - appearBuf->append(") Tj\n"); +AnnotLine::AnnotLine(XRef *xrefA, Catalog *catalog, Dict *dict) : + Annot(xref, catalog, dict), AnnotMarkup(xref, catalog, dict) { - // cleanup - appearBuf->append("ET\n"); - appearBuf->append("Q\n"); + initialize(xrefA, catalog, dict); +} - // next line - y -= 1.1 * fontSize; +AnnotLine::~AnnotLine() { + if (interiorColor) + delete interiorColor; + + if (measure) + delete measure; +} + +void AnnotLine::initialize(XRef *xrefA, Catalog *catalog, Dict *dict) { + Object obj1; + + if (dict->lookup("L", &obj1)->isArray() && obj1.arrayGetLength() == 4) { + 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(); + + } else { + x1 = y1 = x2 = y2 = 0; } + obj1.free(); + + if (dict->lookup("LE", &obj1)->isArray() && obj1.arrayGetLength() == 2) { + Object obj2; - if (daToks) { - deleteGooList(daToks, GooString); + 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(); -// Figure out how much text will fit on the next line. Returns: -// *end = one past the last character to be included -// *width = width of the characters start .. end-1 -// *next = index of first character on the following line -void Annot::getNextLine(GooString *text, int start, - GfxFont *font, double fontSize, double wMax, - int *end, double *width, int *next) { - double w, dw; - int j, k, c; + 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(); - // figure out how much text will fit on the line - //~ what does Adobe do with tabs? - w = 0; - for (j = start; j < text->getLength() && w <= wMax; ++j) { - c = text->getChar(j) & 0xff; - if (c == 0x0a || c == 0x0d) { - break; - } - if (font && !font->isCIDFont()) { - dw = ((Gfx8BitFont *)font)->getWidth(c) * fontSize; + 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()) { + GooString *name = new GooString(obj1.getName()); + + if(!name->cmp("LineArrow")) { + intent = intentLineArrow; + } else if(!name->cmp("LineDimension")) { + intent = intentLineDimension; } else { - // otherwise, make a crude estimate - dw = 0.5 * fontSize; + intent = intentLineArrow; } - w += dw; + delete name; + } else { + intent = intentLineArrow; } - if (w > wMax) { - for (k = j; k > start && text->getChar(k-1) != ' '; --k) ; - for (; k > start && text->getChar(k-1) == ' '; --k) ; - if (k > start) { - j = k; - } - if (j == start) { - // handle the pathological case where the first character is - // too wide to fit on the line all by itself - j = start + 1; - } + obj1.free(); + + if (dict->lookup("LLO", &obj1)->isNum()) { + leaderLineOffset = obj1.getNum(); + + if (leaderLineOffset < 0) + leaderLineOffset = 0; + } else { + leaderLineOffset = 0; } - *end = j; + obj1.free(); + + if (dict->lookup("CP", &obj1)->isName()) { + GooString *name = new GooString(obj1.getName()); - // compute the width - w = 0; - for (k = start; k < j; ++k) { - if (font && !font->isCIDFont()) { - dw = ((Gfx8BitFont *)font)->getWidth(text->getChar(k)) * fontSize; + if(!name->cmp("Inline")) { + captionPos = captionPosInline; + } else if(!name->cmp("Top")) { + captionPos = captionPosTop; } else { - // otherwise, make a crude estimate - dw = 0.5 * fontSize; + captionPos = captionPosInline; } - w += dw; + delete name; + } else { + captionPos = captionPosInline; } - *width = w; + obj1.free(); - // next line - while (j < text->getLength() && text->getChar(j) == ' ') { - ++j; - } - if (j < text->getLength() && text->getChar(j) == 0x0d) { - ++j; + if (dict->lookup("Measure", &obj1)->isDict()) { + measure = obj1.getDict(); + } else { + measure = NULL; } - if (j < text->getLength() && text->getChar(j) == 0x0a) { - ++j; + 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; } - *next = j; + obj1.free(); } -// Draw an (approximate) circle of radius centered at (, ). -// If 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"); +//------------------------------------------------------------------------ +// AnnotTextMarkup +//------------------------------------------------------------------------ + +void AnnotTextMarkup::initialize(XRef *xrefA, Catalog *catalog, Dict *dict) { + Object obj1; + + if(dict->lookup("QuadPoints", &obj1)->isArray()) { + quadrilateralsLength = obj1.arrayGetLength(); + if((quadrilateralsLength % 8) == 0) { + Object obj2; + + quadrilaterals = (AnnotQuadPoints **) gmallocn + ((quadrilateralsLength / 8), sizeof(AnnotQuadPoints *)); + for(int i = 0; i < quadrilateralsLength; i += 8) { + double x1, y1, x2, y2, x3, y3, x4, y4; + + (obj1.arrayGet(i, &obj2)->isNum() ? x1 = obj2.getNum() : x1 = 0); + obj2.free(); + (obj1.arrayGet((i + 1), &obj2)->isNum() ? y1 = obj2.getNum() : y1 = 0); + obj2.free(); + (obj1.arrayGet((i + 2), &obj2)->isNum() ? x2 = obj2.getNum() : x2 = 0); + obj2.free(); + (obj1.arrayGet((i + 3), &obj2)->isNum() ? y2 = obj2.getNum() : y2 = 0); + obj2.free(); + (obj1.arrayGet((i + 4), &obj2)->isNum() ? x3 = obj2.getNum() : x3 = 0); + obj2.free(); + (obj1.arrayGet((i + 5), &obj2)->isNum() ? y3 = obj2.getNum() : y3 = 0); + obj2.free(); + (obj1.arrayGet((i + 6), &obj2)->isNum() ? x4 = obj2.getNum() : x4 = 0); + obj2.free(); + (obj1.arrayGet((i + 7), &obj2)->isNum() ? y4 = obj2.getNum() : y4 = 0); + obj2.free(); + + quadrilaterals[i / 8] = + new AnnotQuadPoints(x1, y1, x2, y2, x3, y3, x4, y4); + } + quadrilateralsLength /= 8; + } else { + quadrilaterals = NULL; + quadrilateralsLength = 0; + } + } else { + quadrilaterals = NULL; + } + obj1.free(); } -// Draw the top-left half of an (approximate) circle of radius -// centered at (, ). -void Annot::drawCircleTopLeft(double cx, double cy, double r) { - double r2; +AnnotTextMarkup::~AnnotTextMarkup() { + if(quadrilaterals) { + for(int i = 0; i < quadrilateralsLength; i++) + delete quadrilaterals[i]; - 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"); + gfree (quadrilaterals); + } } -// Draw the bottom-right half of an (approximate) circle of radius -// centered at (, ). -void Annot::drawCircleBottomRight(double cx, double cy, double r) { - double r2; +//------------------------------------------------------------------------ +// AnnotWidget +//------------------------------------------------------------------------ - 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"); +AnnotWidget::AnnotWidget(XRef *xrefA, Catalog *catalog, Dict *dict) : + Annot(xrefA, catalog, dict) { + initialize(xrefA, catalog, dict); } -// Look up an inheritable field dictionary entry. -Object *Annot::fieldLookup(Dict *field, char *key, Object *obj) { - Dict *dict; - Object parent; +void AnnotWidget::initialize(XRef *xrefA, Catalog *catalog, Dict *dict) { + Object obj1; - dict = field; - if (!dict->lookup(key, obj)->isNull()) { - return obj; + if(dict->lookup("H", &obj1)->isName()) { + GooString *name = new GooString(obj1.getName()); + + if(name->cmp("N")) { + mode = highlightModeNone; + } else if(name->cmp("O")) { + mode = highlightModeOutline; + } else if(name->cmp("P") || name->cmp("T")) { + mode = highlightModePush; + } else { + mode = highlightModeInvert; + } + delete name; + } else { + mode = highlightModeInvert; } - obj->free(); - if (dict->lookup("Parent", &parent)->isDict()) { - fieldLookup(parent.getDict(), key, obj); + obj1.free(); + + if(dict->lookup("MK", &obj1)->isDict()) { + appearCharacs = new AnnotAppearanceCharacs(obj1.getDict()); } else { - obj->initNull(); + appearCharacs = NULL; } - parent.free(); - return obj; -} - -void Annot::draw(Gfx *gfx, GBool printing) { - Object obj; - GBool isLink; - - // check the flags - if ((flags & annotFlagHidden) || - (printing && !(flags & annotFlagPrint)) || - (!printing && (flags & annotFlagNoView))) { - return; + obj1.free(); + + if(dict->lookup("A", &obj1)->isDict()) { + action = obj1.getDict(); + } else { + action = NULL; } - - // draw the appearance stream - isLink = type && !type->cmp("Link"); - appearance.fetch(xref, &obj); - gfx->drawAnnot(&obj, isLink ? borderStyle : (AnnotBorderStyle *)NULL, - xMin, yMin, xMax, yMax); - obj.free(); + obj1.free(); + + if(dict->lookup("AA", &obj1)->isDict()) { + additionActions = obj1.getDict(); + } else { + additionActions = NULL; + } + obj1.free(); } +AnnotWidget::~AnnotWidget() { + if (appearCharacs) + delete appearCharacs; + + if (action) + delete action; + + if (additionActions) + delete additionActions; +} //------------------------------------------------------------------------ // Annots //------------------------------------------------------------------------ Annots::Annots(XRef *xref, Catalog *catalog, Object *annotsObj) { - Dict *acroForm; - Annot *annot; - Object obj1; - int size; - int i; - annots = NULL; - size = 0; - nAnnots = 0; - - acroForm = catalog->getAcroForm()->isDict() ? - catalog->getAcroForm()->getDict() : NULL; if (annotsObj->isArray()) { - for (i = 0; i < annotsObj->arrayGetLength(); ++i) { - //get the Ref to this annot and pass it to Annot constructor - //this way, it'll be possible for the annot to retrieve the corresponding - //form widget - Object obj2; - Ref* pref; + nAnnots = annotsObj->arrayGetLength(); + annots = (Annot **) gmallocn (nAnnots, sizeof (Annot *)); + + for (int i = 0; i < nAnnots; ++i) { + Object obj1; + if (annotsObj->arrayGet(i, &obj1)->isDict()) { - if (annotsObj->arrayGetNF(i, &obj2)->isRef()) - annot = new Annot(xref, acroForm, obj1.getDict(), obj2.getRef(), catalog); - else - annot = new Annot(xref, acroForm, obj1.getDict(), catalog); - if (annot->isOk()) { - if (nAnnots >= size) { - size += 16; - annots = (Annot **)greallocn(annots, size, sizeof(Annot *)); - } - annots[nAnnots++] = annot; - } else { - delete annot; + Object obj2; + + if (annotsObj->arrayGetNF(i, &obj2)->isRef()) { + annots[i] = new Annot(xref, catalog, obj1.getDict(), obj2.getRef()); + } else { + annots[i] = new Annot(xref, catalog, obj1.getDict()); } + obj2.free(); + } else { + annots[i] = NULL; } - obj2.free(); obj1.free(); } + } else { + nAnnots = 0; + annots = NULL; } } @@ -1602,7 +2871,6 @@ return NULL; } - Annots::~Annots() { int i; Index: poppler/Annot.h =================================================================== RCS file: /cvs/poppler/poppler/poppler/Annot.h,v retrieving revision 1.6 diff -u -r1.6 Annot.h --- poppler/Annot.h 25 Apr 2007 19:59:10 -0000 1.6 +++ poppler/Annot.h 28 May 2007 22:52:44 -0000 @@ -19,41 +19,299 @@ class CharCodeToUnicode; class GfxFont; class GfxFontDict; +class PDFRectangle; + +enum AnnotLineEndingStyle { + annotLineEndingSquare, // Square + annotLineEndingCircle, // Circle + annotLineEndingDiamond, // Diamond + annotLineEndingOpenArrow, // OpenArrow + annotLineEndingClosedArrow, // ClosedArrow + annotLineEndingNone, // None + annotLineEndingButt, // Butt + annotLineEndingROpenArrow, // ROpenArrow + annotLineEndingRClosedArrow, // RClosedArrow + annotLineEndingSlash // Slash +}; + +enum AnnotExternalDataType { + annotExternalDataMarkup3D, // Markup3D + annotExternalDataMarkupUnknown +}; //------------------------------------------------------------------------ -// AnnotBorderStyle +// AnnotQuadPoints //------------------------------------------------------------------------ -enum AnnotBorderType { - annotBorderSolid, - annotBorderDashed, - annotBorderBeveled, - annotBorderInset, - annotBorderUnderlined +class AnnotQuadPoints { +public: + + AnnotQuadPoints(double x1, double y1, double x2, double y2, double x3, + double y3, double x4, double y4); + + double getX1() { return x1; } + double getY1() { return y1; } + double getX2() { return x2; } + double getY2() { return y2; } + double getX3() { return x3; } + double getY3() { return y3; } + double getX4() { return x4; } + double getY4() { return y4; } + +protected: + + double x1, y1, x2, y2, x3, y3, x4, y4; }; + +//------------------------------------------------------------------------ +// AnnotCalloutLine +//------------------------------------------------------------------------ -class AnnotBorderStyle { +class AnnotCalloutLine { public: - AnnotBorderStyle(AnnotBorderType typeA, double widthA, - double *dashA, int dashLengthA, - double rA, double gA, double bA); - ~AnnotBorderStyle(); + AnnotCalloutLine(double x1, double y1, double x2, double y2); + + double getX1() { return x1; } + double getY1() { return y1; } + double getX2() { return x2; } + double getY2() { return y2; } + +protected: + + double x1, y1, x2, y2; +}; + +//------------------------------------------------------------------------ +// AnnotCalloutMultiLine +//------------------------------------------------------------------------ + +class AnnotCalloutMultiLine: public AnnotCalloutLine { +public: + + AnnotCalloutMultiLine(double x1, double y1, double x2, double y2, + double x3, double y3); + + double getX3() { return x3; } + double getY3() { return y3; } + +protected: + + double x3, y3; +}; + +//------------------------------------------------------------------------ +// AnnotBorderEffect +//------------------------------------------------------------------------ - AnnotBorderType getType() { return type; } - double getWidth() { return width; } - void getDash(double **dashA, int *dashLengthA) - { *dashA = dash; *dashLengthA = dashLength; } - void getColor(double *rA, double *gA, double *bA) - { *rA = r; *gA = g; *bA = b; } +class AnnotBorderEffect { +public: + + enum AnnotBorderEffectType { + borderEffectNoEffect, // S + borderEffectCloudy // C + }; + + AnnotBorderEffect(Dict *dict); + AnnotBorderEffectType getEffectType() { return effectType; } + double getIntensity() { return intensity; } + private: + + AnnotBorderEffectType effectType; // S (Default S) + double intensity; // I (Default 0) +}; + +//------------------------------------------------------------------------ +// AnnotBorder +//------------------------------------------------------------------------ + +class AnnotBorder { +public: + + virtual ~AnnotBorder(); + + virtual double getWidth() { return width; } + virtual int getDashLength() { return dashLength; } + virtual double *getDash() { return dash; } + +protected: - AnnotBorderType type; double width; double *dash; int dashLength; - double r, g, b; +}; + +//------------------------------------------------------------------------ +// AnnotBorderArray +//------------------------------------------------------------------------ + +class AnnotBorderArray: public AnnotBorder { +public: + + AnnotBorderArray(); + AnnotBorderArray(Array *array); + + virtual double getHorizontalCorner() { return horizontalCorner; } + virtual double getVerticalCorner() { return verticalCorner; } + +protected: + + static const int DASH_LIMIT = 10; // implementation note 82 in Appendix H. + double horizontalCorner; // (Default 0) + double verticalCorner; // (Default 0) + // double width; // (Default 1) (inherited from AnnotBorder) +}; + +//------------------------------------------------------------------------ +// AnnotBorderBS +//------------------------------------------------------------------------ + +class AnnotBorderBS: public AnnotBorder { +public: + + enum AnnotBorderStyle { + borderSolid, // Solid + borderDashed, // Dashed + borderBeveled, // Beveled + borderInset, // Inset + borderUnderlined, // Underlined + }; + + AnnotBorderBS(); + AnnotBorderBS(Dict *dict); + + AnnotBorderStyle getStyle() { return style; } + +private: + + // double width; // W (Default 1) (inherited from AnnotBorder) + AnnotBorderStyle style; // S + // double *dash; // D (Default [3]) (inherited from AnnotBorder) +}; + +//------------------------------------------------------------------------ +// AnnotColor +//------------------------------------------------------------------------ + +class AnnotColor { +public: + + enum AnnotColorSpace { + colorTransparent = 0, + colorGray = 1, + colorRGB = 3, + colorCMYK = 4 + }; + + AnnotColor(); + AnnotColor(Array *array); + ~AnnotColor(); + + AnnotColorSpace getSpace(); + double getValue(int i); + +private: + + double *values; + int length; +}; + +//------------------------------------------------------------------------ +// AnnotIconFit +//------------------------------------------------------------------------ + +class AnnotIconFit { +public: + + enum AnnotIconFitScaleWhen { + scaleAlways, // A + scaleBigger, // B + scaleSmaller, // S + scaleNever // N + }; + + enum AnnotIconFitScale { + scaleAnamorphic, // A + scaleProportional // P + }; + + AnnotIconFit(Dict *dict); + + AnnotIconFitScaleWhen getScaleWhen() { return scaleWhen; } + AnnotIconFitScale getScale() { return scale; } + double getLeft() { return left; } + double getBottom() { return bottom; } + bool getFullyBounds() { return fullyBounds; } + +protected: + + AnnotIconFitScaleWhen scaleWhen; // SW (Default A) + AnnotIconFitScale scale; // S (Default P) + double left; // A (Default [0.5 0.5] + double bottom; // Only if scale is P + bool fullyBounds; // FB (Default false) +}; + +//------------------------------------------------------------------------ +// AnnotAppearance +//------------------------------------------------------------------------ + +class AnnotAppearance { +public: + + enum AnnotAppearanceType { + appearNormal, + appearRollover, + appearDown + }; + + AnnotAppearance(Dict *dict); +}; + +//------------------------------------------------------------------------ +// AnnotAppearanceCharacs +//------------------------------------------------------------------------ + +class AnnotAppearanceCharacs { +public: + + enum AnnotAppearanceCharacsTextPos { + captionNoIcon, // 0 + captionNoCaption, // 1 + captionBelow, // 2 + captionAbove, // 3 + captionRight, // 4 + captionLeft, // 5 + captionOverlaid // 6 + }; + + AnnotAppearanceCharacs(Dict *dict); + ~AnnotAppearanceCharacs(); + + int getRotation() { return rotation; } + AnnotColor *getBorderColor() { return borderColor; } + AnnotColor *getBackColor() { return backColor; } + GooString *getNormalCaption() { return normalCaption; } + GooString *getRolloverCaption() { return rolloverCaption; } + GooString *getAlternateCaption() { return alternateCaption; } + AnnotIconFit *getIconFit() { return iconFit; } + AnnotAppearanceCharacsTextPos getPosition() { return position; } + +protected: + + int rotation; // R (Default 0) + AnnotColor *borderColor; // BC + AnnotColor *backColor; // BG + GooString *normalCaption; // CA + GooString *rolloverCaption; // RC + GooString *alternateCaption; // AC + // I + // RI + // IX + AnnotIconFit *iconFit; // IF + AnnotAppearanceCharacsTextPos position; // TP (Default 0) }; //------------------------------------------------------------------------ @@ -63,9 +321,56 @@ class Annot { public: + enum AnnotFlag { + flagUnknown = 0x0000, + flagInvisible = 0x0001, + flagHidden = 0x0002, + flagPrint = 0x0004, + flagNoZoom = 0x0008, + flagNoRotate = 0x0010, + flagNoView = 0x0020, + flagReadOnly = 0x0040, + flagLocked = 0x0080, + flagToggleNoView = 0x0100, + flagLockedContents = 0x0200 + }; + + enum AnnotSubtype { + typeText, // Text 0 + typeLink, // Link 1 + typeFreeText, // FreeText 2 + typeLine, // Line 3 + typeSquare, // Square 4 + typeCircle, // Circle 5 + typePolygon, // Polygon 6 + typePolyLine, // PolyLine 7 + typeHighlight, // Highlight 8 + typeUnderline, // Underline 9 + typeSquiggly, // Squiggly 10 + typeStrikeOut, // StrikeOut 11 + typeStamp, // Stamp 12 + typeCaret, // Caret 13 + typeInk, // Ink 14 + typePopup, // Popup 15 + typeFileAttachment, // FileAttachment 16 + typeSound, // Sound 17 + typeMovie, // Movie 18 + typeWidget, // Widget 19 + typeScreen, // Screen 20 + typePrinterMark, // PrinterMark 21 + typeTrapNet, // TrapNet 22 + typeWatermark, // Watermark 23 + type3D, // 3D 24 + typeUnknown + }; + + Annot(XRef *xrefA, Catalog *catalog, Dict *dict); + Annot(XRef *xrefA, Catalog *catalog, Dict *dict, const Ref& ref); + Annot(XRef *xrefA, Dict *acroForm, Dict *dict, Catalog *catalog); Annot(XRef *xrefA, Dict *acroForm, Dict *dict, const Ref& aref, Catalog *catalog); - ~Annot(); + virtual ~Annot(); + GBool isOk() { return ok; } void draw(Gfx *gfx, GBool printing); @@ -74,19 +379,33 @@ Object *getAppearance(Object *obj) { return appearance.fetch(xref, obj); } GBool textField() { return isTextField; } - AnnotBorderStyle *getBorderStyle () { return borderStyle; } - GBool match(Ref *refA) { return ref.num == refA->num && ref.gen == refA->gen; } void generateFieldAppearance(Dict *field, Dict *annot, Dict *acroForm); - double getXMin() { return xMin; } - double getYMin() { return yMin; } + double getXMin(); + double getYMin(); double getFontSize() { return fontSize; } -private: + + // getters + AnnotSubtype getType() { return type; } + PDFRectangle *getRect() { return rect; } + GooString *getContents() { return contents; } + Dict *getPageDict() { return pageDict; } + GooString *getAnnotName() { return annotName; } + GooString *getModified() { return modified; } + Guint getFlags() { return flags; } + Dict *getAppearDict() { return appearDict; } + GooString *getAppearState() { return appearState; } + AnnotBorder *getBorder() { return border; } + AnnotColor *getColor() { return color; } + int getTreeKey() { return treeKey; } + Dict *getOptionalContent() { return optionalContent; } + +protected: // void writeTextString (GooString* vStr, CharCodeToUnicode* ccToUnicode, GooString* appearBuf, GfxFont* font); // void generateAppearance(Dict *acroForm, Dict *dict); void setColor(Array *a, GBool fill, int adjust); @@ -106,29 +425,341 @@ void readArrayNum(Object *pdfArray, int key, double *value); // write vStr[i:j[ in appearBuf void writeTextString (GooString *text, GooString *appearBuf, int *i, int j, CharCodeToUnicode *ccToUnicode); - void initialize (XRef *xrefA, Dict *acroForm, Dict *dict, Catalog *catalog); + void initialize (XRef *xrefA, Catalog *catalog, Dict *dict); + + // required data + AnnotSubtype type; // annotation type + PDFRectangle *rect; // Rect + + // optional data + GooString *contents; // Contents + Dict *pageDict; // P + GooString *annotName; // NM + GooString *modified; // M + Guint flags; // F (must be a 32 bit unsigned int) + Dict *appearDict; // AP + GooString *appearState; // AS + AnnotBorder *border; // Border, BS + AnnotColor *color; // C + int treeKey; // Struct Parent; + Dict *optionalContent; // OC XRef *xref; // the xref table for this PDF file Ref ref; // object ref identifying this annotation - GooString *type; // annotation type Object appearance; // a reference to the Form XObject stream // for the normal appearance GooString *appearBuf; - Guint flags; - double xMin, yMin, // annotation rectangle - xMax, yMax; - AnnotBorderStyle *borderStyle; double fontSize; GBool ok; GBool regen, isTextField; GBool isMultiline, isListbox; bool hasRef; - bool hidden; }; //------------------------------------------------------------------------ +// AnnotMarkup +//------------------------------------------------------------------------ + +class AnnotMarkup { +public: + + enum AnnotMarkupReplyType { + replyTypeR, // R + replyTypeGroup // Group + }; + + AnnotMarkup(XRef *xrefA, Catalog *catalog, Dict *dict); + ~AnnotMarkup(); + + // getters + GooString *getLabel() { return label; } + Dict *getPopup() { return popup; } + double getOpacity() { return opacity; } + // getRC + GooString *getDate() { return date; } + Dict *getInReplyTo() { return inReplyTo; } + GooString *getSubject() { return subject; } + AnnotExternalDataType getExData() { return exData; } + +protected: + + GooString *label; // T (Default autor) + Dict *popup; // Popup + double opacity; // CA (Default 1.0) + // RC + GooString *date; // CreationDate + Dict *inReplyTo; // IRT + GooString *subject; // Subj + AnnotMarkupReplyType replyTo; // RT (Default R) + // this object is overrided by the custom intent fields defined in some + // annotation types. + //GooString *intent; // IT + AnnotExternalDataType exData; // ExData + +private: + + void initialize(XRef *xrefA, Catalog *catalog, Dict *dict); +}; + +//------------------------------------------------------------------------ +// AnnotText +//------------------------------------------------------------------------ + +class AnnotText: public Annot, AnnotMarkup { +public: + + enum AnnotTextIcon { + iconComment, // Comment + iconKey, // Key + iconNote, // Note + iconHelp, // Help + iconNewParagraph, // NewParagraph + iconParagraph, // Paragraph + iconInsert // Insert + }; + + enum AnnotTextState { + // Marked state model + stateMarked, // Marked + stateUnmarked, // Unmarked + // Review state model + stateAccepted, // Accepted + stateRejected, // Rejected + stateCancelled, // Cancelled + stateCompleted, // Completed + stateNone, // None + stateUnknown + }; + + AnnotText(XRef *xrefA, Catalog *catalog, Dict *dict); + + // getters + bool getOpen() { return open; } + AnnotTextIcon getIcon() { return icon; } + AnnotTextState getState() { return state; } + + // setters + void setModified(GooString *date); + +private: + + void initialize(XRef *xrefA, Catalog *catalog, Dict *dict); + + bool open; // Open (Default false) + AnnotTextIcon icon; // Name (Default Note) + AnnotTextState state; // State (Default Umarked if + // StateModel Marked + // None if StareModel Review) +}; + +//------------------------------------------------------------------------ +// AnnotLink +//------------------------------------------------------------------------ + +class AnnotLink: public Annot { +public: + + enum AnnotLinkEffect { + effectNone, // N + effectInvert, // I + effectOutline, // O + effectPush // P + }; + + AnnotLink(XRef *xrefA, Catalog *catalog, Dict *dict); + virtual ~AnnotLink(); + + // getters + Dict *getActionDict() { return actionDict; } + // getDest + AnnotLinkEffect getLinkEffect() { return linkEffect; } + Dict *getUriAction() { return uriAction; } + AnnotQuadPoints **getQuadrilaterals() { return quadrilaterals; } + int getQuadrilateralsLength() { return quadrilateralsLength; } + +protected: + + void initialize(XRef *xrefA, Catalog *catalog, Dict *dict); + + Dict *actionDict; // A + //Dest + AnnotLinkEffect linkEffect; // H (Default I) + Dict *uriAction; // PA + + AnnotQuadPoints **quadrilaterals; // QuadPoints + int quadrilateralsLength; +}; + +//------------------------------------------------------------------------ +// AnnotFreeText +//------------------------------------------------------------------------ + +class AnnotFreeText: public Annot, AnnotMarkup { +public: + + enum AnnotFreeTextQuadding { + quaddingLeftJustified, // 0 + quaddingCentered, // 1 + quaddingRightJustified // 2 + }; + + enum AnnotFreeTextIntent { + intentFreeTextCallout, // FreeTextCallout + intentFreeTextTypeWriter // FreeTextTypeWriter + }; + + AnnotFreeText(XRef *xrefA, Catalog *catalog, Dict *dict); + virtual ~AnnotFreeText(); + + // getters + GooString* getAppearanceString() { return appearanceString; } + AnnotFreeTextQuadding getQuadding() { return quadding; } + // return rc + GooString* getStyleString() { return styleString; } + AnnotCalloutLine* getCalloutLine() { return calloutLine; } + AnnotFreeTextIntent getIntent() { return intent; } + AnnotBorderEffect* getBorderEffect() { return borderEffect; } + PDFRectangle* getRectangle() { return rectangle; } + AnnotLineEndingStyle getStartStyle() { return startStyle; } + AnnotLineEndingStyle getEndStyle() { return endStyle; } + +protected: + + void initialize(XRef *xrefA, Catalog *catalog, Dict *dict); + + // required + GooString *appearanceString; // DA + + // optional + AnnotFreeTextQuadding quadding; // Q (Default 0) + // RC + GooString *styleString; // DS + AnnotCalloutLine *calloutLine; // CL + AnnotFreeTextIntent intent; // IT + AnnotBorderEffect *borderEffect; // BE + PDFRectangle *rectangle; // RD + // inherited from Annot + // AnnotBorderBS border; // BS + AnnotLineEndingStyle startStyle; // LE (Default [/None /None]) + AnnotLineEndingStyle endStyle; // +}; + +//------------------------------------------------------------------------ +// AnnotLine +//------------------------------------------------------------------------ + +class AnnotLine: public Annot, AnnotMarkup { +public: + + enum AnnotLineIntent { + intentLineArrow, // LineArrow + intentLineDimension // LineDimension + }; + + enum AnnotLineCaptionPos { + captionPosInline, // Inline + captionPosTop // Top + }; + + AnnotLine(XRef *xrefA, Catalog *catalog, Dict *dict); + ~AnnotLine(); + + // getters + AnnotLineEndingStyle getStartStyle() { return startStyle; } + AnnotLineEndingStyle getEndStyle() { return endStyle; } + AnnotColor *getInteriorColor() { return interiorColor; } + double getLeaderLineLength() { return leaderLineLength; } + double getLeaderLineExtension() { return leaderLineExtension; } + bool getCaption() { return caption; } + AnnotLineIntent getIntent() { return intent; } + double getLeaderLineOffset() { return leaderLineOffset; } + AnnotLineCaptionPos getCaptionPost() { return captionPos; } + Dict *getMeasure() { return measure; } + double getCaptionTextHorizontal() { return captionTextHorizontal; } + double getCaptionTextVertical() { return captionTextVertical; } + +protected: + + void initialize(XRef *xrefA, Catalog *catalog, Dict *dict); + + // required + double x1, y1, x2, y2; // L + + // optional + // inherited from Annot + // AnnotBorderBS border; // BS + AnnotLineEndingStyle startStyle; // LE (Default [/None /None]) + AnnotLineEndingStyle endStyle; // + AnnotColor *interiorColor; // IC + double leaderLineLength; // LL (Default 0) + double leaderLineExtension; // LLE (Default 0) + bool caption; // Cap (Default false) + AnnotLineIntent intent; // IT + double leaderLineOffset; // LLO + AnnotLineCaptionPos captionPos; // CP (Default Inline) + Dict *measure; // Measure + double captionTextHorizontal; // CO (Default [0, 0]) + double captionTextVertical; // +}; + +//------------------------------------------------------------------------ +// AnnotTextMarkup +//------------------------------------------------------------------------ + +class AnnotTextMarkup: public Annot, AnnotMarkup { +public: + + AnnotTextMarkup(XRef *xrefA, Catalog *catalog, Dict *dict); + ~AnnotTextMarkup(); + + AnnotQuadPoints **getQuadrilaterals() { return quadrilaterals; } + int getQuadrilateralsLength() { return quadrilateralsLength; } + +protected: + + void initialize(XRef *xrefA, Catalog *catalog, Dict *dict); + + AnnotQuadPoints **quadrilaterals; // QuadPoints + int quadrilateralsLength; +}; + +//------------------------------------------------------------------------ +// AnnotWidget +//------------------------------------------------------------------------ + +class AnnotWidget: public Annot { +public: + + enum AnnotWidgetHighlightMode { + highlightModeNone, // N + highlightModeInvert, // I + highlightModeOutline, // O + highlightModePush, // P,T + }; + + AnnotWidget(XRef *xrefA, Catalog *catalog, Dict *dict); + ~AnnotWidget(); + + AnnotWidgetHighlightMode getMode() { return mode; } + AnnotAppearanceCharacs *getAppearCharacs() { return appearCharacs; } + Dict *getAction() { return action; } + Dict *getAdditionActions() { return additionActions; } + +protected: + + void initialize(XRef *xrefA, Catalog *catalog, Dict *dict); + + AnnotWidgetHighlightMode mode; // H (Default I) + AnnotAppearanceCharacs *appearCharacs; // MK + Dict *action; // A + Dict *additionActions; // AA + // inherited from Annot + // AnnotBorderBS border; // BS +}; + +//------------------------------------------------------------------------ // Annots //------------------------------------------------------------------------ Index: poppler/FontInfo.cc =================================================================== RCS file: /cvs/poppler/poppler/poppler/FontInfo.cc,v retrieving revision 1.12 diff -u -r1.12 FontInfo.cc --- poppler/FontInfo.cc 25 Apr 2007 19:59:10 -0000 1.12 +++ poppler/FontInfo.cc 28 May 2007 22:52:44 -0000 @@ -50,17 +50,19 @@ } annots = new Annots(doc->getXRef(), doc->getCatalog(), page->getAnnots(&obj1)); obj1.free(); - for (i = 0; i < annots->getNumAnnots(); ++i) { - if (annots->getAnnot(i)->getAppearance(&obj1)->isStream()) { - obj1.streamGetDict()->lookup("Resources", &obj2); - if (obj2.isDict()) { - scanFonts(obj2.getDict(), result); - } - obj2.free(); + if (annots) { + for (i = 0; i < annots->getNumAnnots(); ++i) { + if (annots->getAnnot(i)->getAppearance(&obj1)->isStream()) { + obj1.streamGetDict()->lookup("Resources", &obj2); + if (obj2.isDict()) { + scanFonts(obj2.getDict(), result); + } + obj2.free(); + } + obj1.free(); } - obj1.free(); + delete annots; } - delete annots; } currentPage = lastPage + 1; Index: poppler/Gfx.cc =================================================================== RCS file: /cvs/poppler/poppler/poppler/Gfx.cc,v retrieving revision 1.15 diff -u -r1.15 Gfx.cc --- poppler/Gfx.cc 25 Apr 2007 19:59:10 -0000 1.15 +++ poppler/Gfx.cc 28 May 2007 22:52:48 -0000 @@ -4051,8 +4051,10 @@ // misc //------------------------------------------------------------------------ -void Gfx::drawAnnot(Object *str, AnnotBorderStyle *borderStyle, +void Gfx::drawAnnot(Object *str, AnnotBorder *border, AnnotColor *aColor, double xMin, double yMin, double xMax, double yMax) { + + AnnotBorderBS *borderBS; Dict *dict, *resDict; Object matrixObj, bboxObj, resObj; Object obj1; @@ -4119,9 +4121,9 @@ dict->lookup("Matrix", &matrixObj); if (matrixObj.isArray()) { for (i = 0; i < 6; ++i) { - matrixObj.arrayGet(i, &obj1); - m[i] = obj1.getNum(); - obj1.free(); + matrixObj.arrayGet(i, &obj1); + m[i] = obj1.getNum(); + obj1.free(); } } else { m[0] = 1; m[1] = 0; @@ -4173,13 +4175,19 @@ } // draw the border - if (borderStyle && borderStyle->getWidth() > 0) { + if (border && border->getWidth() > 0) { if (state->getStrokeColorSpace()->getMode() != csDeviceRGB) { state->setStrokePattern(NULL); state->setStrokeColorSpace(new GfxDeviceRGBColorSpace()); out->updateStrokeColorSpace(state); } - borderStyle->getColor(&r, &g, &b); + if (aColor && (aColor->getSpace() == AnnotColor::colorRGB)) { + r = aColor->getValue(0); + g = aColor->getValue(1); + b = aColor->getValue(2); + } else { + r = g = b = 0; + } color.c[0] = dblToCol(r); color.c[1] = dblToCol(g); color.c[2] = dblToCol(b); @@ -4192,13 +4200,16 @@ y = (baseMatrix[0] + baseMatrix[2]) * ictm[1] + (baseMatrix[1] + baseMatrix[3]) * ictm[3]; x = sqrt(0.5 * (x * x + y * y)); - state->setLineWidth(x * borderStyle->getWidth()); + state->setLineWidth(x * border->getWidth()); out->updateLineWidth(state); - borderStyle->getDash(&dash, &dashLength); - if (borderStyle->getType() == annotBorderDashed && dashLength > 0) { + dashLength = border->getDashLength(); + dash = border->getDash(); + borderBS = dynamic_cast (border); + if (borderBS && (borderBS->getStyle() + == AnnotBorderBS::borderDashed) && (dashLength > 0)) { dash2 = (double *)gmallocn(dashLength, sizeof(double)); for (i = 0; i < dashLength; ++i) { - dash2[i] = x * dash[i]; + dash2[i] = x * dash[i]; } state->setLineDash(dash2, dashLength, 0); out->updateLineDash(state); @@ -4207,7 +4218,7 @@ state->clearPath(); state->moveTo(annotX0, out->upsideDown() ? annotY1 : annotY0); state->lineTo(annotX1, out->upsideDown() ? annotY1 : annotY0); - if (borderStyle->getType() != annotBorderUnderlined) { + if (borderBS && (borderBS->getStyle() != AnnotBorderBS::borderUnderlined)) { state->lineTo(annotX1, out->upsideDown() ? annotY0 : annotY1); state->lineTo(annotX0, out->upsideDown() ? annotY0 : annotY1); state->closePath(); Index: poppler/Gfx.h =================================================================== RCS file: /cvs/poppler/poppler/poppler/Gfx.h,v retrieving revision 1.4 diff -u -r1.4 Gfx.h --- poppler/Gfx.h 25 Apr 2007 19:59:10 -0000 1.4 +++ poppler/Gfx.h 28 May 2007 22:52:49 -0000 @@ -41,7 +41,8 @@ class GfxColorSpace; class Gfx; class PDFRectangle; -class AnnotBorderStyle; +class AnnotBorder; +class AnnotColor; //------------------------------------------------------------------------ @@ -128,7 +129,7 @@ // Display an annotation, given its appearance (a Form XObject), // border style, and bounding box (in default user space). - void drawAnnot(Object *str, AnnotBorderStyle *borderStyle, + void drawAnnot(Object *str, AnnotBorder *border, AnnotColor *aColor, double xMin, double yMin, double xMax, double yMax); // Save graphics state. Index: poppler/Page.cc =================================================================== RCS file: /cvs/poppler/poppler/poppler/Page.cc,v retrieving revision 1.19 diff -u -r1.19 Page.cc --- poppler/Page.cc 7 May 2007 18:41:10 -0000 1.19 +++ poppler/Page.cc 28 May 2007 22:52:50 -0000 @@ -314,6 +314,15 @@ contents.free(); } +Annots *Page::getAnnots(Catalog *catalog) { + Annots *annots; + Object obj; + + annots = new Annots(xref, catalog, getAnnots(&obj)); + obj.free(); + return annots; +} + Links *Page::getLinks(Catalog *catalog) { Links *links; Object obj; @@ -454,8 +463,8 @@ } GBool Page::loadThumb(unsigned char **data_out, - int *width_out, int *height_out, - int *rowstride_out) + int *width_out, int *height_out, + int *rowstride_out) { ImageStream *imgstr; unsigned char *pixbufdata; @@ -479,14 +488,14 @@ dict = fetched_thumb.streamGetDict(); str = fetched_thumb.getStream(); - + if (!dict->lookupInt("Width", "W", &width)) goto fail1; if (!dict->lookupInt("Height", "H", &height)) goto fail1; if (!dict->lookupInt("BitsPerComponent", "BPC", &bits)) goto fail1; - + /* Check for invalid dimensions and integer overflow. */ if (width <= 0 || height <= 0) goto fail1; @@ -523,8 +532,8 @@ pixbufdata = (unsigned char *) gmalloc(pixbufdatasize); p = pixbufdata; imgstr = new ImageStream(str, width, - colorMap->getNumPixelComps(), - colorMap->getBits()); + colorMap->getNumPixelComps(), + colorMap->getBits()); imgstr->reset(); for (row = 0; row < height; ++row) { for (col = 0; col < width; ++col) { Index: poppler/Page.h =================================================================== RCS file: /cvs/poppler/poppler/poppler/Page.h,v retrieving revision 1.9 diff -u -r1.9 Page.h --- poppler/Page.h 25 Apr 2007 19:59:10 -0000 1.9 +++ poppler/Page.h 28 May 2007 22:52:50 -0000 @@ -145,6 +145,9 @@ // Get annotations array. Object *getAnnots(Object *obj) { return annots.fetch(xref, obj); } + // Return a list of annots. + Annots *getAnnots(Catalog *catalog); + // Return a list of links. Links *getLinks(Catalog *catalog);