diff options
author | Calvin Morrison <calvin@pobox.com> | 2023-04-05 14:13:39 -0400 |
---|---|---|
committer | Calvin Morrison <calvin@pobox.com> | 2023-04-05 14:13:39 -0400 |
commit | 835e373b3eeaabcd0621ed6798ab500f37982fae (patch) | |
tree | dfa16b0e2e1b4956b38f693220eac4e607802133 /xpdf/Annot.cc |
Diffstat (limited to 'xpdf/Annot.cc')
-rw-r--r-- | xpdf/Annot.cc | 1448 |
1 files changed, 1448 insertions, 0 deletions
diff --git a/xpdf/Annot.cc b/xpdf/Annot.cc new file mode 100644 index 0000000..82ef600 --- /dev/null +++ b/xpdf/Annot.cc @@ -0,0 +1,1448 @@ +//======================================================================== +// +// Annot.cc +// +// Copyright 2000-2003 Glyph & Cog, LLC +// +//======================================================================== + +#include <aconf.h> + +#ifdef USE_GCC_PRAGMAS +#pragma implementation +#endif + +#include <stdlib.h> +#include <math.h> +#include "gmem.h" +#include "gmempp.h" +#include "GList.h" +#include "Error.h" +#include "Object.h" +#include "Catalog.h" +#include "Gfx.h" +#include "GfxFont.h" +#include "Lexer.h" +#include "PDFDoc.h" +#include "OptionalContent.h" +#include "AcroForm.h" +#include "BuiltinFontTables.h" +#include "FontEncodingTables.h" +#include "Annot.h" + +// the MSVC math.h doesn't define this +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +//------------------------------------------------------------------------ + +#define annotFlagHidden 0x0002 +#define annotFlagPrint 0x0004 +#define annotFlagNoView 0x0020 + +// distance of Bezier control point from center for circle approximation +// = (4 * (sqrt(2) - 1) / 3) * r +#define bezierCircle 0.55228475 + +#define lineEndSize1 6 +#define lineEndSize2 10 +#define lineArrowAngle (M_PI / 6) + +//------------------------------------------------------------------------ +// AnnotBorderStyle +//------------------------------------------------------------------------ + +AnnotBorderStyle::AnnotBorderStyle(AnnotBorderType typeA, double widthA, + double *dashA, int dashLengthA, + double *colorA, int nColorCompsA) { + type = typeA; + width = widthA; + dash = dashA; + dashLength = dashLengthA; + color[0] = colorA[0]; + color[1] = colorA[1]; + color[2] = colorA[2]; + color[3] = colorA[3]; + nColorComps = nColorCompsA; +} + +AnnotBorderStyle::~AnnotBorderStyle() { + if (dash) { + gfree(dash); + } +} + +//------------------------------------------------------------------------ +// Annot +//------------------------------------------------------------------------ + +Annot::Annot(PDFDoc *docA, Dict *dict, Ref *refA) { + Object apObj, asObj, obj1, obj2, obj3; + AnnotBorderType borderType; + double borderWidth; + double *borderDash; + int borderDashLength; + double borderColor[4]; + int nBorderColorComps; + double t; + int i; + + ok = gTrue; + doc = docA; + xref = doc->getXRef(); + ref = *refA; + type = NULL; + appearanceState = NULL; + appearBuf = NULL; + borderStyle = NULL; + + //----- parse the type + + if (dict->lookup("Subtype", &obj1)->isName()) { + type = new GString(obj1.getName()); + } + obj1.free(); + + //----- parse the rectangle + + if (dict->lookup("Rect", &obj1)->isArray() && + obj1.arrayGetLength() == 4) { + xMin = yMin = xMax = yMax = 0; + if (obj1.arrayGet(0, &obj2)->isNum()) { + xMin = obj2.getNum(); + } + obj2.free(); + if (obj1.arrayGet(1, &obj2)->isNum()) { + yMin = obj2.getNum(); + } + obj2.free(); + if (obj1.arrayGet(2, &obj2)->isNum()) { + xMax = obj2.getNum(); + } + obj2.free(); + if (obj1.arrayGet(3, &obj2)->isNum()) { + yMax = obj2.getNum(); + } + obj2.free(); + if (xMin > xMax) { + t = xMin; xMin = xMax; xMax = t; + } + if (yMin > yMax) { + t = yMin; yMin = yMax; yMax = t; + } + } else { + error(errSyntaxError, -1, "Bad bounding box for annotation"); + ok = gFalse; + } + obj1.free(); + + //----- parse the flags + + if (dict->lookup("F", &obj1)->isInt()) { + flags = obj1.getInt(); + } else { + flags = 0; + } + obj1.free(); + + //----- parse the border style + + borderType = annotBorderSolid; + borderWidth = 1; + borderDash = NULL; + borderDashLength = 0; + nBorderColorComps = 3; + borderColor[0] = 0; + borderColor[1] = 0; + borderColor[2] = 1; + borderColor[3] = 0; + 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 (i = 0; i < borderDashLength; ++i) { + if (obj2.arrayGet(i, &obj3)->isNum()) { + borderDash[i] = obj3.getNum(); + } else { + borderDash[i] = 1; + } + obj3.free(); + } + } + obj2.free(); + } 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 (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; + } + obj2.free(); + } + } else { + // an empty Border array also means "no border" + borderWidth = 0; + } + } + } + obj1.free(); + // Acrobat ignores borders with unreasonable widths + if (borderWidth > 1 && (borderWidth > xMax - xMin || + borderWidth > yMax - yMin)) { + borderWidth = 0; + } + if (dict->lookup("C", &obj1)->isArray() && + (obj1.arrayGetLength() == 1 || + obj1.arrayGetLength() == 3 || + obj1.arrayGetLength() == 4)) { + nBorderColorComps = obj1.arrayGetLength(); + for (i = 0; i < nBorderColorComps; ++i) { + if (obj1.arrayGet(i, &obj2)->isNum()) { + borderColor[i] = obj2.getNum(); + } else { + borderColor[i] = 0; + } + obj2.free(); + } + } + obj1.free(); + borderStyle = new AnnotBorderStyle(borderType, borderWidth, + borderDash, borderDashLength, + borderColor, nBorderColorComps); + + //----- get the appearance state + + dict->lookup("AP", &apObj); + dict->lookup("AS", &asObj); + if (asObj.isName()) { + appearanceState = new GString(asObj.getName()); + } else if (apObj.isDict()) { + apObj.dictLookup("N", &obj1); + if (obj1.isDict() && obj1.dictGetLength() == 1) { + appearanceState = new GString(obj1.dictGetKey(0)); + } + obj1.free(); + } + if (!appearanceState) { + appearanceState = new GString("Off"); + } + asObj.free(); + + //----- get the annotation appearance + + if (apObj.isDict()) { + apObj.dictLookup("N", &obj1); + apObj.dictLookupNF("N", &obj2); + if (obj1.isDict()) { + if (obj1.dictLookupNF(appearanceState->getCString(), &obj3)->isRef()) { + obj3.copy(&appearance); + } + obj3.free(); + } else if (obj2.isRef()) { + obj2.copy(&appearance); + } + obj1.free(); + obj2.free(); + } + apObj.free(); + + //----- get the optional content entry + + dict->lookupNF("OC", &ocObj); +} + +Annot::~Annot() { + if (type) { + delete type; + } + if (appearanceState) { + delete appearanceState; + } + appearance.free(); + if (appearBuf) { + delete appearBuf; + } + if (borderStyle) { + delete borderStyle; + } + ocObj.free(); +} + +void Annot::generateAnnotAppearance() { + Object obj; + + appearance.fetch(doc->getXRef(), &obj); + if (!obj.isStream()) { + if (type) { + if (!type->cmp("Line")) { + generateLineAppearance(); + } else if (!type->cmp("PolyLine")) { + generatePolyLineAppearance(); + } else if (!type->cmp("Polygon")) { + generatePolygonAppearance(); + } else if (!type->cmp("FreeText")) { + generateFreeTextAppearance(); + } + } + } + obj.free(); +} + +//~ this doesn't draw the caption +void Annot::generateLineAppearance() { + Object annotObj, gfxStateDict, appearDict, obj1, obj2; + MemStream *appearStream; + double x1, y1, x2, y2, dx, dy, len, w; + double lx1, ly1, lx2, ly2; + double tx1, ty1, tx2, ty2; + double ax1, ay1, ax2, ay2; + double bx1, by1, bx2, by2; + double leaderLen, leaderExtLen, leaderOffLen; + AnnotLineEndType lineEnd1, lineEnd2; + GBool fill; + + if (!getObject(&annotObj)->isDict()) { + annotObj.free(); + return; + } + + appearBuf = new GString(); + + //----- check for transparency + if (annotObj.dictLookup("CA", &obj1)->isNum()) { + gfxStateDict.initDict(doc->getXRef()); + gfxStateDict.dictAdd(copyString("ca"), obj1.copy(&obj2)); + appearBuf->append("/GS1 gs\n"); + } + obj1.free(); + + //----- set line style, colors + setLineStyle(borderStyle, &w); + setStrokeColor(borderStyle->getColor(), borderStyle->getNumColorComps()); + fill = gFalse; + if (annotObj.dictLookup("IC", &obj1)->isArray()) { + if (setFillColor(&obj1)) { + fill = gTrue; + } + } + obj1.free(); + + //----- get line properties + if (annotObj.dictLookup("L", &obj1)->isArray() && + obj1.arrayGetLength() == 4) { + if (obj1.arrayGet(0, &obj2)->isNum()) { + x1 = obj2.getNum(); + } else { + obj2.free(); + obj1.free(); + goto err1; + } + obj2.free(); + if (obj1.arrayGet(1, &obj2)->isNum()) { + y1 = obj2.getNum(); + } else { + obj2.free(); + obj1.free(); + goto err1; + } + obj2.free(); + if (obj1.arrayGet(2, &obj2)->isNum()) { + x2 = obj2.getNum(); + } else { + obj2.free(); + obj1.free(); + goto err1; + } + obj2.free(); + if (obj1.arrayGet(3, &obj2)->isNum()) { + y2 = obj2.getNum(); + } else { + obj2.free(); + obj1.free(); + goto err1; + } + obj2.free(); + } else { + obj1.free(); + goto err1; + } + obj1.free(); + lineEnd1 = lineEnd2 = annotLineEndNone; + if (annotObj.dictLookup("LE", &obj1)->isArray() && + obj1.arrayGetLength() == 2) { + lineEnd1 = parseLineEndType(obj1.arrayGet(0, &obj2)); + obj2.free(); + lineEnd2 = parseLineEndType(obj1.arrayGet(1, &obj2)); + obj2.free(); + } + obj1.free(); + if (annotObj.dictLookup("LL", &obj1)->isNum()) { + leaderLen = obj1.getNum(); + } else { + leaderLen = 0; + } + obj1.free(); + if (annotObj.dictLookup("LLE", &obj1)->isNum()) { + leaderExtLen = obj1.getNum(); + } else { + leaderExtLen = 0; + } + obj1.free(); + if (annotObj.dictLookup("LLO", &obj1)->isNum()) { + leaderOffLen = obj1.getNum(); + } else { + leaderOffLen = 0; + } + obj1.free(); + + //----- compute positions + x1 -= xMin; + y1 -= yMin; + x2 -= xMin; + y2 -= yMin; + dx = x2 - x1; + dy = y2 - y1; + len = sqrt(dx*dx + dy*dy); + if (len > 0) { + dx /= len; + dy /= len; + } + if (leaderLen != 0) { + ax1 = x1 + leaderOffLen * dy; + ay1 = y1 - leaderOffLen * dx; + lx1 = ax1 + leaderLen * dy; + ly1 = ay1 - leaderLen * dx; + bx1 = lx1 + leaderExtLen * dy; + by1 = ly1 - leaderExtLen * dx; + ax2 = x2 + leaderOffLen * dy; + ay2 = y2 - leaderOffLen * dx; + lx2 = ax2 + leaderLen * dy; + ly2 = ay2 - leaderLen * dx; + bx2 = lx2 + leaderExtLen * dy; + by2 = ly2 - leaderExtLen * dx; + } else { + lx1 = x1; + ly1 = y1; + lx2 = x2; + ly2 = y2; + ax1 = ay1 = ax2 = ay2 = 0; // make gcc happy + bx1 = by1 = bx2 = by2 = 0; + } + adjustLineEndpoint(lineEnd1, lx1, ly1, dx, dy, w, &tx1, &ty1); + adjustLineEndpoint(lineEnd2, lx2, ly2, -dx, -dy, w, &tx2, &ty2); + + //----- draw leaders + if (leaderLen != 0) { + appearBuf->appendf("{0:.4f} {1:.4f} m {2:.4f} {3:.4f} l\n", + ax1, ay1, bx1, by1); + appearBuf->appendf("{0:.4f} {1:.4f} m {2:.4f} {3:.4f} l\n", + ax2, ay2 , bx2, by2); + } + + //----- draw the line + appearBuf->appendf("{0:.4f} {1:.4f} m {2:.4f} {3:.4f} l\n", + tx1, ty1, tx2, ty2); + appearBuf->append("S\n"); + + //----- draw the arrows + if (borderStyle->getType() == annotBorderDashed) { + appearBuf->append("[] 0 d\n"); + } + drawLineArrow(lineEnd1, lx1, ly1, dx, dy, w, fill); + drawLineArrow(lineEnd2, lx2, ly2, -dx, -dy, w, fill); + + //----- build the appearance stream dictionary + appearDict.initDict(doc->getXRef()); + appearDict.dictAdd(copyString("Length"), + obj1.initInt(appearBuf->getLength())); + appearDict.dictAdd(copyString("Subtype"), obj1.initName("Form")); + obj1.initArray(doc->getXRef()); + 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); + if (gfxStateDict.isDict()) { + obj1.initDict(doc->getXRef()); + obj2.initDict(doc->getXRef()); + obj2.dictAdd(copyString("GS1"), &gfxStateDict); + obj1.dictAdd(copyString("ExtGState"), &obj2); + appearDict.dictAdd(copyString("Resources"), &obj1); + } + + //----- build the appearance stream + appearStream = new MemStream(appearBuf->getCString(), 0, + appearBuf->getLength(), &appearDict); + appearance.free(); + appearance.initStream(appearStream); + + err1: + annotObj.free(); +} + +//~ this doesn't handle line ends (arrows) +void Annot::generatePolyLineAppearance() { + Object annotObj, gfxStateDict, appearDict, obj1, obj2; + MemStream *appearStream; + double x1, y1, w; + int i; + + if (!getObject(&annotObj)->isDict()) { + annotObj.free(); + return; + } + + appearBuf = new GString(); + + //----- check for transparency + if (annotObj.dictLookup("CA", &obj1)->isNum()) { + gfxStateDict.initDict(doc->getXRef()); + gfxStateDict.dictAdd(copyString("ca"), obj1.copy(&obj2)); + appearBuf->append("/GS1 gs\n"); + } + obj1.free(); + + //----- set line style, colors + setLineStyle(borderStyle, &w); + setStrokeColor(borderStyle->getColor(), borderStyle->getNumColorComps()); + // fill = gFalse; + // if (annotObj.dictLookup("IC", &obj1)->isArray()) { + // if (setFillColor(&obj1)) { + // fill = gTrue; + // } + // } + // obj1.free(); + + //----- draw line + if (!annotObj.dictLookup("Vertices", &obj1)->isArray()) { + obj1.free(); + goto err1; + } + for (i = 0; i+1 < obj1.arrayGetLength(); i += 2) { + if (!obj1.arrayGet(i, &obj2)->isNum()) { + obj2.free(); + obj1.free(); + goto err1; + } + x1 = obj2.getNum(); + obj2.free(); + if (!obj1.arrayGet(i+1, &obj2)->isNum()) { + obj2.free(); + obj1.free(); + goto err1; + } + y1 = obj2.getNum(); + obj2.free(); + x1 -= xMin; + y1 -= yMin; + if (i == 0) { + appearBuf->appendf("{0:.4f} {1:.4f} m\n", x1, y1); + } else { + appearBuf->appendf("{0:.4f} {1:.4f} l\n", x1, y1); + } + } + appearBuf->append("S\n"); + obj1.free(); + + //----- build the appearance stream dictionary + appearDict.initDict(doc->getXRef()); + appearDict.dictAdd(copyString("Length"), + obj1.initInt(appearBuf->getLength())); + appearDict.dictAdd(copyString("Subtype"), obj1.initName("Form")); + obj1.initArray(doc->getXRef()); + 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); + if (gfxStateDict.isDict()) { + obj1.initDict(doc->getXRef()); + obj2.initDict(doc->getXRef()); + obj2.dictAdd(copyString("GS1"), &gfxStateDict); + obj1.dictAdd(copyString("ExtGState"), &obj2); + appearDict.dictAdd(copyString("Resources"), &obj1); + } + + //----- build the appearance stream + appearStream = new MemStream(appearBuf->getCString(), 0, + appearBuf->getLength(), &appearDict); + appearance.free(); + appearance.initStream(appearStream); + + err1: + annotObj.free(); +} + +void Annot::generatePolygonAppearance() { + Object annotObj, gfxStateDict, appearDict, obj1, obj2; + MemStream *appearStream; + double x1, y1; + int i; + + if (!getObject(&annotObj)->isDict()) { + annotObj.free(); + return; + } + + appearBuf = new GString(); + + //----- check for transparency + if (annotObj.dictLookup("CA", &obj1)->isNum()) { + gfxStateDict.initDict(doc->getXRef()); + gfxStateDict.dictAdd(copyString("ca"), obj1.copy(&obj2)); + appearBuf->append("/GS1 gs\n"); + } + obj1.free(); + + //----- set fill color + if (!annotObj.dictLookup("IC", &obj1)->isArray() || + !setFillColor(&obj1)) { + obj1.free(); + goto err1; + } + obj1.free(); + + //----- fill polygon + if (!annotObj.dictLookup("Vertices", &obj1)->isArray()) { + obj1.free(); + goto err1; + } + for (i = 0; i+1 < obj1.arrayGetLength(); i += 2) { + if (!obj1.arrayGet(i, &obj2)->isNum()) { + obj2.free(); + obj1.free(); + goto err1; + } + x1 = obj2.getNum(); + obj2.free(); + if (!obj1.arrayGet(i+1, &obj2)->isNum()) { + obj2.free(); + obj1.free(); + goto err1; + } + y1 = obj2.getNum(); + obj2.free(); + x1 -= xMin; + y1 -= yMin; + if (i == 0) { + appearBuf->appendf("{0:.4f} {1:.4f} m\n", x1, y1); + } else { + appearBuf->appendf("{0:.4f} {1:.4f} l\n", x1, y1); + } + } + appearBuf->append("f\n"); + obj1.free(); + + //----- build the appearance stream dictionary + appearDict.initDict(doc->getXRef()); + appearDict.dictAdd(copyString("Length"), + obj1.initInt(appearBuf->getLength())); + appearDict.dictAdd(copyString("Subtype"), obj1.initName("Form")); + obj1.initArray(doc->getXRef()); + 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); + if (gfxStateDict.isDict()) { + obj1.initDict(doc->getXRef()); + obj2.initDict(doc->getXRef()); + obj2.dictAdd(copyString("GS1"), &gfxStateDict); + obj1.dictAdd(copyString("ExtGState"), &obj2); + appearDict.dictAdd(copyString("Resources"), &obj1); + } + + //----- build the appearance stream + appearStream = new MemStream(appearBuf->getCString(), 0, + appearBuf->getLength(), &appearDict); + appearance.free(); + appearance.initStream(appearStream); + + err1: + annotObj.free(); +} + +//~ this doesn't handle rich text +//~ this doesn't handle the callout +//~ this doesn't handle the RD field +void Annot::generateFreeTextAppearance() { + Object annotObj, gfxStateDict, appearDict, obj1, obj2; + Object resources, gsResources, fontResources, defaultFont; + GString *text, *da; + double lineWidth; + int quadding, rot; + MemStream *appearStream; + + if (!getObject(&annotObj)->isDict()) { + annotObj.free(); + return; + } + + appearBuf = new GString(); + + //----- check for transparency + if (annotObj.dictLookup("CA", &obj1)->isNum()) { + gfxStateDict.initDict(doc->getXRef()); + gfxStateDict.dictAdd(copyString("ca"), obj1.copy(&obj2)); + appearBuf->append("/GS1 gs\n"); + } + obj1.free(); + + //----- draw the text + if (annotObj.dictLookup("Contents", &obj1)->isString()) { + text = obj1.getString()->copy(); + } else { + text = new GString(); + } + obj1.free(); + if (annotObj.dictLookup("Q", &obj1)->isInt()) { + quadding = obj1.getInt(); + } else { + quadding = 0; + } + obj1.free(); + if (annotObj.dictLookup("DA", &obj1)->isString()) { + da = obj1.getString()->copy(); + } else { + da = new GString(); + } + obj1.free(); + // the "Rotate" field is not defined in the PDF spec, but Acrobat + // looks at it + if (annotObj.dictLookup("Rotate", &obj1)->isInt()) { + rot = obj1.getInt(); + } else { + rot = 0; + } + obj1.free(); + drawText(text, da, quadding, 0, rot); + delete text; + delete da; + + //----- draw the border + if (borderStyle->getWidth() != 0) { + setLineStyle(borderStyle, &lineWidth); + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} re s\n", + 0.5 * lineWidth, 0.5 * lineWidth, + xMax - xMin - lineWidth, yMax - yMin - lineWidth); + } + + //----- build the appearance stream dictionary + appearDict.initDict(doc->getXRef()); + appearDict.dictAdd(copyString("Length"), + obj1.initInt(appearBuf->getLength())); + appearDict.dictAdd(copyString("Subtype"), obj1.initName("Form")); + obj1.initArray(doc->getXRef()); + 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); + resources.initDict(doc->getXRef()); + defaultFont.initDict(doc->getXRef()); + defaultFont.dictAdd(copyString("Type"), obj1.initName("Font")); + defaultFont.dictAdd(copyString("Subtype"), obj1.initName("Type1")); + defaultFont.dictAdd(copyString("BaseFont"), obj1.initName("Helvetica")); + defaultFont.dictAdd(copyString("Encoding"), obj1.initName("WinAnsiEncoding")); + fontResources.initDict(doc->getXRef()); + fontResources.dictAdd(copyString("xpdf_default_font"), &defaultFont); + resources.dictAdd(copyString("Font"), &fontResources); + if (gfxStateDict.isDict()) { + gsResources.initDict(doc->getXRef()); + gsResources.dictAdd(copyString("GS1"), &gfxStateDict); + resources.dictAdd(copyString("ExtGState"), &gsResources); + } + appearDict.dictAdd(copyString("Resources"), &resources); + + //----- build the appearance stream + appearStream = new MemStream(appearBuf->getCString(), 0, + appearBuf->getLength(), &appearDict); + appearance.free(); + appearance.initStream(appearStream); + + annotObj.free(); +} + +void Annot::setLineStyle(AnnotBorderStyle *bs, double *lineWidth) { + double *dash; + double w; + int dashLength, i; + + if ((w = borderStyle->getWidth()) <= 0) { + w = 0.1; + } + *lineWidth = w; + appearBuf->appendf("{0:.4f} w\n", w); + // this treats beveled/inset/underline as solid + if (borderStyle->getType() == annotBorderDashed) { + borderStyle->getDash(&dash, &dashLength); + appearBuf->append("["); + for (i = 0; i < dashLength; ++i) { + appearBuf->appendf(" {0:.4f}", dash[i]); + } + appearBuf->append("] 0 d\n"); + } + appearBuf->append("0 j\n0 J\n"); +} + +void Annot::setStrokeColor(double *color, int nComps) { + switch (nComps) { + case 0: + appearBuf->append("0 G\n"); + break; + case 1: + appearBuf->appendf("{0:.2f} G\n", color[0]); + break; + case 3: + appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} RG\n", + color[0], color[1], color[2]); + break; + case 4: + appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} K\n", + color[0], color[1], color[2], color[3]); + break; + } +} + +GBool Annot::setFillColor(Object *colorObj) { + Object obj; + double color[4]; + int i; + + if (!colorObj->isArray()) { + return gFalse; + } + for (i = 0; i < colorObj->arrayGetLength() && i < 4; ++i) { + if (colorObj->arrayGet(i, &obj)->isNum()) { + color[i] = obj.getNum(); + } else { + color[i] = 0; + } + obj.free(); + } + switch (colorObj->arrayGetLength()) { + case 1: + appearBuf->appendf("{0:.2f} g\n", color[0]); + return gTrue; + case 3: + appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} rg\n", + color[0], color[1], color[2]); + return gTrue; + case 4: + appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.3f} k\n", + color[0], color[1], + color[2], color[3]); + return gTrue; + } + return gFalse; +} + +AnnotLineEndType Annot::parseLineEndType(Object *obj) { + if (obj->isName("None")) { + return annotLineEndNone; + } else if (obj->isName("Square")) { + return annotLineEndSquare; + } else if (obj->isName("Circle")) { + return annotLineEndCircle; + } else if (obj->isName("Diamond")) { + return annotLineEndDiamond; + } else if (obj->isName("OpenArrow")) { + return annotLineEndOpenArrow; + } else if (obj->isName("ClosedArrow")) { + return annotLineEndClosedArrow; + } else if (obj->isName("Butt")) { + return annotLineEndButt; + } else if (obj->isName("ROpenArrow")) { + return annotLineEndROpenArrow; + } else if (obj->isName("RClosedArrow")) { + return annotLineEndRClosedArrow; + } else if (obj->isName("Slash")) { + return annotLineEndSlash; + } else { + return annotLineEndNone; + } +} + +void Annot::adjustLineEndpoint(AnnotLineEndType lineEnd, + double x, double y, double dx, double dy, + double w, double *tx, double *ty) { + switch (lineEnd) { + case annotLineEndNone: + w = 0; + break; + case annotLineEndSquare: + w *= lineEndSize1; + break; + case annotLineEndCircle: + w *= lineEndSize1; + break; + case annotLineEndDiamond: + w *= lineEndSize1; + break; + case annotLineEndOpenArrow: + w = 0; + break; + case annotLineEndClosedArrow: + w *= lineEndSize2 * cos(lineArrowAngle); + break; + case annotLineEndButt: + w = 0; + break; + case annotLineEndROpenArrow: + w *= lineEndSize2 * cos(lineArrowAngle); + break; + case annotLineEndRClosedArrow: + w *= lineEndSize2 * cos(lineArrowAngle); + break; + case annotLineEndSlash: + w = 0; + break; + } + *tx = x + w * dx; + *ty = y + w * dy; +} + +void Annot::drawLineArrow(AnnotLineEndType lineEnd, + double x, double y, double dx, double dy, + double w, GBool fill) { + switch (lineEnd) { + case annotLineEndNone: + break; + case annotLineEndSquare: + w *= lineEndSize1; + appearBuf->appendf("{0:.4f} {1:.4f} m\n", + x + w*dx + 0.5*w*dy, + y + w*dy - 0.5*w*dx); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x + 0.5*w*dy, + y - 0.5*w*dx); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x - 0.5*w*dy, + y + 0.5*w*dx); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x + w*dx - 0.5*w*dy, + y + w*dy + 0.5*w*dx); + appearBuf->append(fill ? "b\n" : "s\n"); + break; + case annotLineEndCircle: + w *= lineEndSize1; + drawCircle(x + 0.5*w*dx, y + 0.5*w*dy, 0.5*w, fill ? "b" : "s"); + break; + case annotLineEndDiamond: + w *= lineEndSize1; + appearBuf->appendf("{0:.4f} {1:.4f} m\n", x, y); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x + 0.5*w*dx - 0.5*w*dy, + y + 0.5*w*dy + 0.5*w*dx); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x + w*dx, + y + w*dy); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x + 0.5*w*dx + 0.5*w*dy, + y + 0.5*w*dy - 0.5*w*dx); + appearBuf->append(fill ? "b\n" : "s\n"); + break; + case annotLineEndOpenArrow: + w *= lineEndSize2; + appearBuf->appendf("{0:.4f} {1:.4f} m\n", + x + w*cos(lineArrowAngle)*dx + w*sin(lineArrowAngle)*dy, + y + w*cos(lineArrowAngle)*dy - w*sin(lineArrowAngle)*dx); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", x, y); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x + w*cos(lineArrowAngle)*dx - w*sin(lineArrowAngle)*dy, + y + w*cos(lineArrowAngle)*dy + w*sin(lineArrowAngle)*dx); + appearBuf->append("S\n"); + break; + case annotLineEndClosedArrow: + w *= lineEndSize2; + appearBuf->appendf("{0:.4f} {1:.4f} m\n", + x + w*cos(lineArrowAngle)*dx + w*sin(lineArrowAngle)*dy, + y + w*cos(lineArrowAngle)*dy - w*sin(lineArrowAngle)*dx); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", x, y); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x + w*cos(lineArrowAngle)*dx - w*sin(lineArrowAngle)*dy, + y + w*cos(lineArrowAngle)*dy + w*sin(lineArrowAngle)*dx); + appearBuf->append(fill ? "b\n" : "s\n"); + break; + case annotLineEndButt: + w *= lineEndSize1; + appearBuf->appendf("{0:.4f} {1:.4f} m\n", + x + 0.5*w*dy, + y - 0.5*w*dx); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x - 0.5*w*dy, + y + 0.5*w*dx); + appearBuf->append("S\n"); + break; + case annotLineEndROpenArrow: + w *= lineEndSize2; + appearBuf->appendf("{0:.4f} {1:.4f} m\n", + x + w*sin(lineArrowAngle)*dy, + y - w*sin(lineArrowAngle)*dx); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x + w*cos(lineArrowAngle)*dx, + y + w*cos(lineArrowAngle)*dy); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x - w*sin(lineArrowAngle)*dy, + y + w*sin(lineArrowAngle)*dx); + appearBuf->append("S\n"); + break; + case annotLineEndRClosedArrow: + w *= lineEndSize2; + appearBuf->appendf("{0:.4f} {1:.4f} m\n", + x + w*sin(lineArrowAngle)*dy, + y - w*sin(lineArrowAngle)*dx); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x + w*cos(lineArrowAngle)*dx, + y + w*cos(lineArrowAngle)*dy); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x - w*sin(lineArrowAngle)*dy, + y + w*sin(lineArrowAngle)*dx); + appearBuf->append(fill ? "b\n" : "s\n"); + break; + case annotLineEndSlash: + w *= lineEndSize1; + appearBuf->appendf("{0:.4f} {1:.4f} m\n", + x + 0.5*w*cos(lineArrowAngle)*dy + - 0.5*w*sin(lineArrowAngle)*dx, + y - 0.5*w*cos(lineArrowAngle)*dx + - 0.5*w*sin(lineArrowAngle)*dy); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x - 0.5*w*cos(lineArrowAngle)*dy + + 0.5*w*sin(lineArrowAngle)*dx, + y + 0.5*w*cos(lineArrowAngle)*dx + + 0.5*w*sin(lineArrowAngle)*dy); + appearBuf->append("S\n"); + break; + } +} + +// Draw an (approximate) circle of radius <r> centered at (<cx>, <cy>). +// <cmd> is used to draw the circle ("f", "s", or "b"). +void Annot::drawCircle(double cx, double cy, double r, const char *cmd) { + appearBuf->appendf("{0:.4f} {1:.4f} m\n", + cx + r, cy); + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", + cx + r, cy + bezierCircle * r, + cx + bezierCircle * r, cy + r, + cx, cy + r); + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", + cx - bezierCircle * r, cy + r, + cx - r, cy + bezierCircle * r, + cx - r, cy); + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", + cx - r, cy - bezierCircle * r, + cx - bezierCircle * r, cy - r, + cx, cy - r); + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", + cx + bezierCircle * r, cy - r, + cx + r, cy - bezierCircle * r, + cx + r, cy); + appearBuf->appendf("{0:s}\n", cmd); +} + +// Draw the top-left half of an (approximate) circle of radius <r> +// centered at (<cx>, <cy>). +void Annot::drawCircleTopLeft(double cx, double cy, double r) { + double r2; + + r2 = r / sqrt(2.0); + appearBuf->appendf("{0:.4f} {1:.4f} m\n", + cx + r2, cy + r2); + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} 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:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", + cx - (1 + bezierCircle) * r2, + cy + (1 - bezierCircle) * r2, + cx - (1 + bezierCircle) * r2, + cy - (1 - bezierCircle) * r2, + cx - r2, + cy - r2); + appearBuf->append("S\n"); +} + +// Draw the bottom-right half of an (approximate) circle of radius <r> +// centered at (<cx>, <cy>). +void Annot::drawCircleBottomRight(double cx, double cy, double r) { + double r2; + + r2 = r / sqrt(2.0); + appearBuf->appendf("{0:.4f} {1:.4f} m\n", + cx - r2, cy - r2); + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} 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:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", + cx + (1 + bezierCircle) * r2, + cy - (1 - bezierCircle) * r2, + cx + (1 + bezierCircle) * r2, + cy + (1 - bezierCircle) * r2, + cx + r2, + cy + r2); + appearBuf->append("S\n"); +} + +void Annot::drawText(GString *text, GString *da, int quadding, double margin, + int rot) { + GString *text2, *tok; + GList *daToks; + const char *charName; + double dx, dy, fontSize, fontSize2, x, y, w; + Gushort charWidth; + int tfPos, tmPos, i, j, c; + + // check for a Unicode string + //~ this currently drops all non-Latin1 characters + if (text->getLength() >= 2 && + text->getChar(0) == '\xfe' && text->getChar(1) == '\xff') { + text2 = new GString(); + for (i = 2; i+1 < text->getLength(); i += 2) { + c = ((text->getChar(i) & 0xff) << 8) + (text->getChar(i+1) & 0xff); + if (c <= 0xff) { + text2->append((char)c); + } else { + text2->append('?'); + } + } + } else { + text2 = text; + } + + // parse the default appearance string + tfPos = tmPos = -1; + if (da) { + daToks = new GList(); + 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 GString(da, i, j - i)); + i = j; + } + } + for (i = 2; i < daToks->getLength(); ++i) { + if (i >= 2 && !((GString *)daToks->get(i))->cmp("Tf")) { + tfPos = i - 2; + } else if (i >= 6 && !((GString *)daToks->get(i))->cmp("Tm")) { + tmPos = i - 6; + } + } + } else { + daToks = NULL; + } + + // get the font and font size + fontSize = 0; + if (tfPos >= 0) { + //~ where do we look up the font? + tok = (GString *)daToks->get(tfPos); + tok->clear(); + tok->append("/xpdf_default_font"); + tok = (GString *)daToks->get(tfPos + 1); + fontSize = atof(tok->getCString()); + } else { + error(errSyntaxError, -1, + "Missing 'Tf' operator in annotation's DA string"); + daToks->append(new GString("/xpdf_default_font")); + daToks->append(new GString("10")); + daToks->append(new GString("Tf")); + } + + // setup + appearBuf->append("q\n"); + if (rot == 90) { + appearBuf->appendf("0 1 -1 0 {0:.4f} 0 cm\n", xMax - xMin); + dx = yMax - yMin; + dy = xMax - xMin; + } else if (rot == 180) { + appearBuf->appendf("-1 0 0 -1 {0:.4f} {1:.4f} cm\n", + xMax - xMin, yMax - yMin); + dx = xMax - yMax; + dy = yMax - yMin; + } else if (rot == 270) { + appearBuf->appendf("0 -1 1 0 0 {0:.4f} cm\n", yMax - yMin); + dx = yMax - yMin; + dy = xMax - xMin; + } else { // assume rot == 0 + dx = xMax - xMin; + dy = yMax - yMin; + } + appearBuf->append("BT\n"); + + // compute string width + //~ this assumes we're substituting Helvetica/WinAnsiEncoding for everything + w = 0; + for (i = 0; i < text2->getLength(); ++i) { + charName = winAnsiEncoding[text->getChar(i) & 0xff]; + if (charName && builtinFonts[4].widths->getWidth(charName, &charWidth)) { + w += charWidth; + } else { + w += 0.5; + } + } + + // compute font autosize + if (fontSize == 0) { + fontSize = dy - 2 * margin; + fontSize2 = (dx - 2 * margin) / w; + if (fontSize2 < fontSize) { + fontSize = fontSize2; + } + fontSize = floor(fontSize); + if (tfPos >= 0) { + tok = (GString *)daToks->get(tfPos + 1); + tok->clear(); + tok->appendf("{0:.4f}", fontSize); + } + } + + // compute text start position + w *= fontSize; + switch (quadding) { + case 0: + default: + x = margin + 2; + break; + case 1: + x = (dx - w) / 2; + break; + case 2: + x = dx - margin - 2 - w; + break; + } + y = 0.5 * dy - 0.4 * fontSize; + + // set the font matrix + if (tmPos >= 0) { + tok = (GString *)daToks->get(tmPos + 4); + tok->clear(); + tok->appendf("{0:.4f}", x); + tok = (GString *)daToks->get(tmPos + 5); + tok->clear(); + tok->appendf("{0:.4f}", y); + } + + // write the DA string + if (daToks) { + for (i = 0; i < daToks->getLength(); ++i) { + appearBuf->append((GString *)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:.4f} {1:.4f} Tm\n", x, y); + } + + // write the text string + appearBuf->append('('); + for (i = 0; i < text2->getLength(); ++i) { + c = text2->getChar(i) & 0xff; + if (c == '(' || c == ')' || c == '\\') { + appearBuf->append('\\'); + appearBuf->append((char)c); + } else if (c < 0x20 || c >= 0x80) { + appearBuf->appendf("\\{0:03o}", c); + } else { + appearBuf->append((char)c); + } + } + appearBuf->append(") Tj\n"); + + // cleanup + appearBuf->append("ET\n"); + appearBuf->append("Q\n"); + + if (daToks) { + deleteGList(daToks, GString); + } + if (text2 != text) { + delete text2; + } +} + +void Annot::draw(Gfx *gfx, GBool printing) { + GBool oc, isLink; + + // check the flags + if ((flags & annotFlagHidden) || + (printing && !(flags & annotFlagPrint)) || + (!printing && (flags & annotFlagNoView))) { + return; + } + + // check the optional content entry + if (doc->getOptionalContent()->evalOCObject(&ocObj, &oc) && !oc) { + return; + } + + // draw the appearance stream + isLink = type && !type->cmp("Link"); + gfx->drawAnnot(&appearance, isLink ? borderStyle : (AnnotBorderStyle *)NULL, + xMin, yMin, xMax, yMax); +} + +Object *Annot::getObject(Object *obj) { + if (ref.num >= 0) { + xref->fetch(ref.num, ref.gen, obj); + } else { + obj->initNull(); + } + return obj; +} + +//------------------------------------------------------------------------ +// Annots +//------------------------------------------------------------------------ + +Annots::Annots(PDFDoc *docA, Object *annotsObj) { + Annot *annot; + Object obj1, obj2; + Ref ref; + GBool drawWidgetAnnots; + int size; + int i; + + doc = docA; + annots = NULL; + size = 0; + nAnnots = 0; + + if (annotsObj->isArray()) { + // Kludge: some PDF files define an empty AcroForm, but still + // include Widget-type annotations -- in that case, we want to + // draw the widgets (since the form code won't). This really + // ought to look for Widget-type annotations that are not included + // in any form field. + drawWidgetAnnots = !doc->getCatalog()->getForm() || + doc->getCatalog()->getForm()->getNumFields() == 0; + for (i = 0; i < annotsObj->arrayGetLength(); ++i) { + if (annotsObj->arrayGetNF(i, &obj1)->isRef()) { + ref = obj1.getRef(); + obj1.free(); + annotsObj->arrayGet(i, &obj1); + } else { + ref.num = ref.gen = -1; + } + if (obj1.isDict()) { + if (drawWidgetAnnots || + !obj1.dictLookup("Subtype", &obj2)->isName("Widget")) { + annot = new Annot(doc, obj1.getDict(), &ref); + if (annot->isOk()) { + if (nAnnots >= size) { + size += 16; + annots = (Annot **)greallocn(annots, size, sizeof(Annot *)); + } + annots[nAnnots++] = annot; + } else { + delete annot; + } + } + obj2.free(); + } + obj1.free(); + } + } +} + +Annots::~Annots() { + int i; + + for (i = 0; i < nAnnots; ++i) { + delete annots[i]; + } + gfree(annots); +} + +Annot *Annots::find(double x, double y) { + int i; + + for (i = nAnnots - 1; i >= 0; --i) { + if (annots[i]->inRect(x, y)) { + return annots[i]; + } + } + return NULL; +} + +int Annots::findIdx(double x, double y) { + int i; + + for (i = nAnnots - 1; i >= 0; --i) { + if (annots[i]->inRect(x, y)) { + return i; + } + } + return -1; +} + +void Annots::generateAnnotAppearances() { + int i; + + for (i = 0; i < nAnnots; ++i) { + annots[i]->generateAnnotAppearance(); + } +} + +Annot *Annots::findAnnot(Ref *ref) { + int i; + + for (i = 0; i < nAnnots; ++i) { + if (annots[i]->match(ref)) { + return annots[i]; + } + } + return NULL; +} |