aboutsummaryrefslogtreecommitdiff
path: root/xpdf/AcroForm.cc
diff options
context:
space:
mode:
authorCalvin Morrison <calvin@pobox.com>2023-04-05 14:13:39 -0400
committerCalvin Morrison <calvin@pobox.com>2023-04-05 14:13:39 -0400
commit835e373b3eeaabcd0621ed6798ab500f37982fae (patch)
treedfa16b0e2e1b4956b38f693220eac4e607802133 /xpdf/AcroForm.cc
xpdf-no-select-disableHEADmaster
Diffstat (limited to 'xpdf/AcroForm.cc')
-rw-r--r--xpdf/AcroForm.cc3889
1 files changed, 3889 insertions, 0 deletions
diff --git a/xpdf/AcroForm.cc b/xpdf/AcroForm.cc
new file mode 100644
index 0000000..7b2b985
--- /dev/null
+++ b/xpdf/AcroForm.cc
@@ -0,0 +1,3889 @@
+//========================================================================
+//
+// AcroForm.cc
+//
+// Copyright 2012 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 "GString.h"
+#include "GList.h"
+#include "GlobalParams.h"
+#include "Error.h"
+#include "Object.h"
+#include "PDFDoc.h"
+#include "TextString.h"
+#include "Gfx.h"
+#include "GfxFont.h"
+#include "OptionalContent.h"
+#include "Annot.h"
+#include "Lexer.h"
+#include "XFAScanner.h"
+#include "UTF8.h"
+#include "PDF417Barcode.h"
+#include "AcroForm.h"
+
+//------------------------------------------------------------------------
+
+#define acroFormFlagReadOnly (1 << 0) // all
+#define acroFormFlagRequired (1 << 1) // all
+#define acroFormFlagNoExport (1 << 2) // all
+#define acroFormFlagMultiline (1 << 12) // text
+#define acroFormFlagPassword (1 << 13) // text
+#define acroFormFlagNoToggleToOff (1 << 14) // button
+#define acroFormFlagRadio (1 << 15) // button
+#define acroFormFlagPushbutton (1 << 16) // button
+#define acroFormFlagCombo (1 << 17) // choice
+#define acroFormFlagEdit (1 << 18) // choice
+#define acroFormFlagSort (1 << 19) // choice
+#define acroFormFlagFileSelect (1 << 20) // text
+#define acroFormFlagMultiSelect (1 << 21) // choice
+#define acroFormFlagDoNotSpellCheck (1 << 22) // text, choice
+#define acroFormFlagDoNotScroll (1 << 23) // text
+#define acroFormFlagComb (1 << 24) // text
+#define acroFormFlagRadiosInUnison (1 << 25) // button
+#define acroFormFlagRichText (1 << 25) // text
+#define acroFormFlagCommitOnSelChange (1 << 26) // choice
+
+#define acroFormQuadLeft 0
+#define acroFormQuadCenter 1
+#define acroFormQuadRight 2
+
+#define acroFormVAlignTop 0
+#define acroFormVAlignMiddle 1
+#define acroFormVAlignMiddleNoDescender 2
+#define acroFormVAlignBottom 3
+
+#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
+
+// limit recursive field-parent lookups to avoid infinite loops
+#define maxFieldObjectDepth 50
+
+//------------------------------------------------------------------------
+
+// 5 bars + 5 spaces -- each can be wide (1) or narrow (0)
+// (there are always exactly 3 wide elements;
+// the last space is always narrow)
+static Guchar code3Of9Data[128][10] = {
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 0x00
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 0x10
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 1, 1, 0, 0, 0, 1, 0, 0, 0 }, // ' ' = 0x20
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 1, 0, 1, 0, 1, 0, 0, 0, 0 }, // '$' = 0x24
+ { 0, 0, 0, 1, 0, 1, 0, 1, 0, 0 }, // '%' = 0x25
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 1, 0, 0, 1, 0, 1, 0, 0, 0 }, // '*' = 0x2a
+ { 0, 1, 0, 0, 0, 1, 0, 1, 0, 0 }, // '+' = 0x2b
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 1, 0, 0, 0, 0, 1, 0, 1, 0 }, // '-' = 0x2d
+ { 1, 1, 0, 0, 0, 0, 1, 0, 0, 0 }, // '.' = 0x2e
+ { 0, 1, 0, 1, 0, 0, 0, 1, 0, 0 }, // '/' = 0x2f
+ { 0, 0, 0, 1, 1, 0, 1, 0, 0, 0 }, // '0' = 0x30
+ { 1, 0, 0, 1, 0, 0, 0, 0, 1, 0 }, // '1'
+ { 0, 0, 1, 1, 0, 0, 0, 0, 1, 0 }, // '2'
+ { 1, 0, 1, 1, 0, 0, 0, 0, 0, 0 }, // '3'
+ { 0, 0, 0, 1, 1, 0, 0, 0, 1, 0 }, // '4'
+ { 1, 0, 0, 1, 1, 0, 0, 0, 0, 0 }, // '5'
+ { 0, 0, 1, 1, 1, 0, 0, 0, 0, 0 }, // '6'
+ { 0, 0, 0, 1, 0, 0, 1, 0, 1, 0 }, // '7'
+ { 1, 0, 0, 1, 0, 0, 1, 0, 0, 0 }, // '8'
+ { 0, 0, 1, 1, 0, 0, 1, 0, 0, 0 }, // '9'
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 0x40
+ { 1, 0, 0, 0, 0, 1, 0, 0, 1, 0 }, // 'A' = 0x41
+ { 0, 0, 1, 0, 0, 1, 0, 0, 1, 0 }, // 'B'
+ { 1, 0, 1, 0, 0, 1, 0, 0, 0, 0 }, // 'C'
+ { 0, 0, 0, 0, 1, 1, 0, 0, 1, 0 }, // 'D'
+ { 1, 0, 0, 0, 1, 1, 0, 0, 0, 0 }, // 'E'
+ { 0, 0, 1, 0, 1, 1, 0, 0, 0, 0 }, // 'F'
+ { 0, 0, 0, 0, 0, 1, 1, 0, 1, 0 }, // 'G'
+ { 1, 0, 0, 0, 0, 1, 1, 0, 0, 0 }, // 'H'
+ { 0, 0, 1, 0, 0, 1, 1, 0, 0, 0 }, // 'I'
+ { 0, 0, 0, 0, 1, 1, 1, 0, 0, 0 }, // 'J'
+ { 1, 0, 0, 0, 0, 0, 0, 1, 1, 0 }, // 'K'
+ { 0, 0, 1, 0, 0, 0, 0, 1, 1, 0 }, // 'L'
+ { 1, 0, 1, 0, 0, 0, 0, 1, 0, 0 }, // 'M'
+ { 0, 0, 0, 0, 1, 0, 0, 1, 1, 0 }, // 'N'
+ { 1, 0, 0, 0, 1, 0, 0, 1, 0, 0 }, // 'O'
+ { 0, 0, 1, 0, 1, 0, 0, 1, 0, 0 }, // 'P' = 0x50
+ { 0, 0, 0, 0, 0, 0, 1, 1, 1, 0 }, // 'Q'
+ { 1, 0, 0, 0, 0, 0, 1, 1, 0, 0 }, // 'R'
+ { 0, 0, 1, 0, 0, 0, 1, 1, 0, 0 }, // 'S'
+ { 0, 0, 0, 0, 1, 0, 1, 1, 0, 0 }, // 'T'
+ { 1, 1, 0, 0, 0, 0, 0, 0, 1, 0 }, // 'U'
+ { 0, 1, 1, 0, 0, 0, 0, 0, 1, 0 }, // 'V'
+ { 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 }, // 'W'
+ { 0, 1, 0, 0, 1, 0, 0, 0, 1, 0 }, // 'X'
+ { 1, 1, 0, 0, 1, 0, 0, 0, 0, 0 }, // 'Y'
+ { 0, 1, 1, 0, 1, 0, 0, 0, 0, 0 }, // 'Z'
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 0x60
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 0x70
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
+};
+
+// 3 bars + 3 spaces -- each can be 1, 2, 3, or 4 units wide
+static Guchar code128Data[107][6] = {
+ { 2, 1, 2, 2, 2, 2 },
+ { 2, 2, 2, 1, 2, 2 },
+ { 2, 2, 2, 2, 2, 1 },
+ { 1, 2, 1, 2, 2, 3 },
+ { 1, 2, 1, 3, 2, 2 },
+ { 1, 3, 1, 2, 2, 2 },
+ { 1, 2, 2, 2, 1, 3 },
+ { 1, 2, 2, 3, 1, 2 },
+ { 1, 3, 2, 2, 1, 2 },
+ { 2, 2, 1, 2, 1, 3 },
+ { 2, 2, 1, 3, 1, 2 },
+ { 2, 3, 1, 2, 1, 2 },
+ { 1, 1, 2, 2, 3, 2 },
+ { 1, 2, 2, 1, 3, 2 },
+ { 1, 2, 2, 2, 3, 1 },
+ { 1, 1, 3, 2, 2, 2 },
+ { 1, 2, 3, 1, 2, 2 },
+ { 1, 2, 3, 2, 2, 1 },
+ { 2, 2, 3, 2, 1, 1 },
+ { 2, 2, 1, 1, 3, 2 },
+ { 2, 2, 1, 2, 3, 1 },
+ { 2, 1, 3, 2, 1, 2 },
+ { 2, 2, 3, 1, 1, 2 },
+ { 3, 1, 2, 1, 3, 1 },
+ { 3, 1, 1, 2, 2, 2 },
+ { 3, 2, 1, 1, 2, 2 },
+ { 3, 2, 1, 2, 2, 1 },
+ { 3, 1, 2, 2, 1, 2 },
+ { 3, 2, 2, 1, 1, 2 },
+ { 3, 2, 2, 2, 1, 1 },
+ { 2, 1, 2, 1, 2, 3 },
+ { 2, 1, 2, 3, 2, 1 },
+ { 2, 3, 2, 1, 2, 1 },
+ { 1, 1, 1, 3, 2, 3 },
+ { 1, 3, 1, 1, 2, 3 },
+ { 1, 3, 1, 3, 2, 1 },
+ { 1, 1, 2, 3, 1, 3 },
+ { 1, 3, 2, 1, 1, 3 },
+ { 1, 3, 2, 3, 1, 1 },
+ { 2, 1, 1, 3, 1, 3 },
+ { 2, 3, 1, 1, 1, 3 },
+ { 2, 3, 1, 3, 1, 1 },
+ { 1, 1, 2, 1, 3, 3 },
+ { 1, 1, 2, 3, 3, 1 },
+ { 1, 3, 2, 1, 3, 1 },
+ { 1, 1, 3, 1, 2, 3 },
+ { 1, 1, 3, 3, 2, 1 },
+ { 1, 3, 3, 1, 2, 1 },
+ { 3, 1, 3, 1, 2, 1 },
+ { 2, 1, 1, 3, 3, 1 },
+ { 2, 3, 1, 1, 3, 1 },
+ { 2, 1, 3, 1, 1, 3 },
+ { 2, 1, 3, 3, 1, 1 },
+ { 2, 1, 3, 1, 3, 1 },
+ { 3, 1, 1, 1, 2, 3 },
+ { 3, 1, 1, 3, 2, 1 },
+ { 3, 3, 1, 1, 2, 1 },
+ { 3, 1, 2, 1, 1, 3 },
+ { 3, 1, 2, 3, 1, 1 },
+ { 3, 3, 2, 1, 1, 1 },
+ { 3, 1, 4, 1, 1, 1 },
+ { 2, 2, 1, 4, 1, 1 },
+ { 4, 3, 1, 1, 1, 1 },
+ { 1, 1, 1, 2, 2, 4 },
+ { 1, 1, 1, 4, 2, 2 },
+ { 1, 2, 1, 1, 2, 4 },
+ { 1, 2, 1, 4, 2, 1 },
+ { 1, 4, 1, 1, 2, 2 },
+ { 1, 4, 1, 2, 2, 1 },
+ { 1, 1, 2, 2, 1, 4 },
+ { 1, 1, 2, 4, 1, 2 },
+ { 1, 2, 2, 1, 1, 4 },
+ { 1, 2, 2, 4, 1, 1 },
+ { 1, 4, 2, 1, 1, 2 },
+ { 1, 4, 2, 2, 1, 1 },
+ { 2, 4, 1, 2, 1, 1 },
+ { 2, 2, 1, 1, 1, 4 },
+ { 4, 1, 3, 1, 1, 1 },
+ { 2, 4, 1, 1, 1, 2 },
+ { 1, 3, 4, 1, 1, 1 },
+ { 1, 1, 1, 2, 4, 2 },
+ { 1, 2, 1, 1, 4, 2 },
+ { 1, 2, 1, 2, 4, 1 },
+ { 1, 1, 4, 2, 1, 2 },
+ { 1, 2, 4, 1, 1, 2 },
+ { 1, 2, 4, 2, 1, 1 },
+ { 4, 1, 1, 2, 1, 2 },
+ { 4, 2, 1, 1, 1, 2 },
+ { 4, 2, 1, 2, 1, 1 },
+ { 2, 1, 2, 1, 4, 1 },
+ { 2, 1, 4, 1, 2, 1 },
+ { 4, 1, 2, 1, 2, 1 },
+ { 1, 1, 1, 1, 4, 3 },
+ { 1, 1, 1, 3, 4, 1 },
+ { 1, 3, 1, 1, 4, 1 },
+ { 1, 1, 4, 1, 1, 3 },
+ { 1, 1, 4, 3, 1, 1 },
+ { 4, 1, 1, 1, 1, 3 },
+ { 4, 1, 1, 3, 1, 1 },
+ { 1, 1, 3, 1, 4, 1 },
+ { 1, 1, 4, 1, 3, 1 },
+ { 3, 1, 1, 1, 4, 1 },
+ { 4, 1, 1, 1, 3, 1 },
+ { 2, 1, 1, 4, 1, 2 }, // start code A
+ { 2, 1, 1, 2, 1, 4 }, // start code B
+ { 2, 1, 1, 2, 3, 2 }, // start code C
+ { 2, 3, 3, 1, 1, 1 } // stop code (without final bar)
+};
+
+//------------------------------------------------------------------------
+
+// map an annotation ref to a page number
+class AcroFormAnnotPage {
+public:
+ AcroFormAnnotPage(int annotNumA, int annotGenA, int pageNumA)
+ { annotNum = annotNumA; annotGen = annotGenA; pageNum = pageNumA; }
+ int annotNum;
+ int annotGen;
+ int pageNum;
+};
+
+//------------------------------------------------------------------------
+// AcroForm
+//------------------------------------------------------------------------
+
+AcroForm *AcroForm::load(PDFDoc *docA, Catalog *catalog, Object *acroFormObjA) {
+ Object acroFormObj2;
+ AcroForm *acroForm;
+ AcroFormField *field;
+ Object xfaObj, fieldsObj, annotsObj, annotRef, annotObj, obj1, obj2;
+ int pageNum, i, j;
+
+ // this is the normal case: acroFormObj is a dictionary, as expected
+ if (acroFormObjA->isDict()) {
+ acroForm = new AcroForm(docA, acroFormObjA);
+
+ if (globalParams->getEnableXFA()) {
+ if (!acroFormObjA->dictLookup("XFA", &xfaObj)->isNull()) {
+ acroForm->xfaScanner = XFAScanner::load(&xfaObj);
+ if (!catalog->getNeedsRendering()) {
+ acroForm->isStaticXFA = gTrue;
+ }
+ }
+ xfaObj.free();
+ }
+
+ if (acroFormObjA->dictLookup("NeedAppearances", &obj1)->isBool()) {
+ acroForm->needAppearances = obj1.getBool();
+ }
+ obj1.free();
+
+ acroForm->buildAnnotPageList(catalog);
+
+ if (!acroFormObjA->dictLookup("Fields", &obj1)->isArray()) {
+ if (!obj1.isNull()) {
+ error(errSyntaxError, -1, "AcroForm Fields entry is wrong type");
+ }
+ obj1.free();
+ delete acroForm;
+ return NULL;
+ }
+ for (i = 0; i < obj1.arrayGetLength(); ++i) {
+ obj1.arrayGetNF(i, &obj2);
+ acroForm->scanField(&obj2);
+ obj2.free();
+ }
+ obj1.free();
+
+ // scan the annotations, looking for Widget-type annots that are
+ // not attached to the AcroForm object
+ for (pageNum = 1; pageNum <= catalog->getNumPages(); ++pageNum) {
+ if (catalog->getPage(pageNum)->getAnnots(&annotsObj)->isArray()) {
+ for (i = 0; i < annotsObj.arrayGetLength(); ++i) {
+ if (annotsObj.arrayGetNF(i, &annotRef)->isRef()) {
+ for (j = 0; j < acroForm->fields->getLength(); ++j) {
+ field = (AcroFormField *)acroForm->fields->get(j);
+ if (field->fieldRef.isRef()) {
+ if (field->fieldRef.getRefNum() == annotRef.getRefNum() &&
+ field->fieldRef.getRefGen() == annotRef.getRefGen()) {
+ break;
+ }
+ }
+ }
+ if (j == acroForm->fields->getLength()) {
+ annotRef.fetch(acroForm->doc->getXRef(), &annotObj);
+ if (annotObj.isDict()) {
+ if (annotObj.dictLookup("Subtype", &obj1)->isName("Widget")) {
+ acroForm->scanField(&annotRef);
+ }
+ obj1.free();
+ }
+ annotObj.free();
+ }
+ }
+ annotRef.free();
+ }
+ }
+ annotsObj.free();
+ }
+
+ // if acroFormObjA is a null object, but there are Widget-type
+ // annots, we still create an AcroForm
+ } else {
+ // create an empty dict for acroFormObj
+ acroFormObj2.initDict(docA->getXRef());
+ acroForm = new AcroForm(docA, &acroFormObj2);
+ acroFormObj2.free();
+
+ acroForm->buildAnnotPageList(catalog);
+
+ // scan the annotations, looking for any Widget-type annots
+ for (pageNum = 1; pageNum <= catalog->getNumPages(); ++pageNum) {
+ if (catalog->getPage(pageNum)->getAnnots(&annotsObj)->isArray()) {
+ for (i = 0; i < annotsObj.arrayGetLength(); ++i) {
+ if (annotsObj.arrayGetNF(i, &annotRef)->isRef()) {
+ annotRef.fetch(acroForm->doc->getXRef(), &annotObj);
+ if (annotObj.isDict()) {
+ if (annotObj.dictLookup("Subtype", &obj1)->isName("Widget")) {
+ acroForm->scanField(&annotRef);
+ }
+ obj1.free();
+ }
+ annotObj.free();
+ }
+ annotRef.free();
+ }
+ }
+ annotsObj.free();
+ }
+
+ if (acroForm->fields->getLength() == 0) {
+ delete acroForm;
+ acroForm = NULL;
+ }
+ }
+
+ return acroForm;
+}
+
+AcroForm::AcroForm(PDFDoc *docA, Object *acroFormObjA) {
+ doc = docA;
+ acroFormObjA->copy(&acroFormObj);
+ needAppearances = gFalse;
+ annotPages = new GList();
+ fields = new GList();
+ xfaScanner = NULL;
+ isStaticXFA = gFalse;
+}
+
+AcroForm::~AcroForm() {
+ acroFormObj.free();
+ deleteGList(annotPages, AcroFormAnnotPage);
+ deleteGList(fields, AcroFormField);
+ delete xfaScanner;
+}
+
+const char *AcroForm::getType() {
+ return isStaticXFA ? "static XFA" : "AcroForm";
+}
+
+void AcroForm::buildAnnotPageList(Catalog *catalog) {
+ Object annotsObj, annotObj;
+ int pageNum, i;
+
+ for (pageNum = 1; pageNum <= catalog->getNumPages(); ++pageNum) {
+ if (catalog->getPage(pageNum)->getAnnots(&annotsObj)->isArray()) {
+ for (i = 0; i < annotsObj.arrayGetLength(); ++i) {
+ if (annotsObj.arrayGetNF(i, &annotObj)->isRef()) {
+ annotPages->append(new AcroFormAnnotPage(annotObj.getRefNum(),
+ annotObj.getRefGen(),
+ pageNum));
+ }
+ annotObj.free();
+ }
+ }
+ annotsObj.free();
+ }
+ //~ sort the list
+}
+
+int AcroForm::lookupAnnotPage(Object *annotRef) {
+ AcroFormAnnotPage *annotPage;
+ int num, gen, i;
+
+ if (!annotRef->isRef()) {
+ return 0;
+ }
+ num = annotRef->getRefNum();
+ gen = annotRef->getRefGen();
+ //~ use bin search
+ for (i = 0; i < annotPages->getLength(); ++i) {
+ annotPage = (AcroFormAnnotPage *)annotPages->get(i);
+ if (annotPage->annotNum == num && annotPage->annotGen == gen) {
+ return annotPage->pageNum;
+ }
+ }
+ return 0;
+}
+
+void AcroForm::scanField(Object *fieldRef) {
+ AcroFormField *field;
+ Object fieldObj, kidsObj, kidRef, kidObj, subtypeObj;
+ GBool isTerminal;
+ int i;
+
+ fieldRef->fetch(doc->getXRef(), &fieldObj);
+ if (!fieldObj.isDict()) {
+ error(errSyntaxError, -1, "AcroForm field object is wrong type");
+ fieldObj.free();
+ return;
+ }
+
+ // if this field has a Kids array, and all of the kids have a Parent
+ // reference (i.e., they're all form fields, not widget
+ // annotations), then this is a non-terminal field, and we need to
+ // scan the kids
+ isTerminal = gTrue;
+ if (fieldObj.dictLookup("Kids", &kidsObj)->isArray()) {
+ isTerminal = gFalse;
+ for (i = 0; !isTerminal && i < kidsObj.arrayGetLength(); ++i) {
+ kidsObj.arrayGet(i, &kidObj);
+ if (kidObj.isDict()) {
+ if (kidObj.dictLookup("Parent", &subtypeObj)->isNull()) {
+ isTerminal = gTrue;
+ }
+ subtypeObj.free();
+ }
+ kidObj.free();
+ }
+ if (!isTerminal) {
+ for (i = 0; !isTerminal && i < kidsObj.arrayGetLength(); ++i) {
+ kidsObj.arrayGetNF(i, &kidRef);
+ scanField(&kidRef);
+ kidRef.free();
+ }
+ }
+ }
+ kidsObj.free();
+
+ if (isTerminal) {
+ if ((field = AcroFormField::load(this, fieldRef))) {
+ fields->append(field);
+ }
+ }
+
+ fieldObj.free();
+}
+
+void AcroForm::draw(int pageNum, Gfx *gfx, GBool printing) {
+ int i;
+
+ for (i = 0; i < fields->getLength(); ++i) {
+ ((AcroFormField *)fields->get(i))->draw(pageNum, gfx, printing);
+ }
+}
+
+int AcroForm::getNumFields() {
+ return fields->getLength();
+}
+
+AcroFormField *AcroForm::getField(int idx) {
+ return (AcroFormField *)fields->get(idx);
+}
+
+AcroFormField *AcroForm::findField(int pg, double x, double y) {
+ AcroFormField *field;
+ double llx, lly, urx, ury;
+ int i;
+
+ for (i = 0; i < fields->getLength(); ++i) {
+ field = (AcroFormField *)fields->get(i);
+ if (field->getPageNum() == pg) {
+ field->getBBox(&llx, &lly, &urx, &ury);
+ if (llx <= x && x <= urx && lly <= y && y <= ury) {
+ return field;
+ }
+ }
+ }
+ return NULL;
+}
+
+int AcroForm::findFieldIdx(int pg, double x, double y) {
+ AcroFormField *field;
+ double llx, lly, urx, ury;
+ int i;
+
+ for (i = 0; i < fields->getLength(); ++i) {
+ field = (AcroFormField *)fields->get(i);
+ if (field->getPageNum() == pg) {
+ field->getBBox(&llx, &lly, &urx, &ury);
+ if (llx <= x && x <= urx && lly <= y && y <= ury) {
+ return i;
+ }
+ }
+ }
+ return -1;
+}
+
+//------------------------------------------------------------------------
+// AcroFormField
+//------------------------------------------------------------------------
+
+AcroFormField *AcroFormField::load(AcroForm *acroFormA, Object *fieldRefA) {
+ GString *typeStr;
+ TextString *nameA;
+ GString *xfaName;
+ Guint flagsA;
+ GBool haveFlags, typeFromParentA;
+ Object fieldObjA, parentObj, parentObj2, obj1, obj2;
+ AcroFormFieldType typeA;
+ XFAField *xfaFieldA;
+ AcroFormField *field;
+ int depth, i0, i1;
+
+ fieldRefA->fetch(acroFormA->doc->getXRef(), &fieldObjA);
+
+ //----- get field info
+
+ if (fieldObjA.dictLookup("T", &obj1)->isString()) {
+ nameA = new TextString(obj1.getString());
+ } else {
+ nameA = new TextString();
+ }
+ obj1.free();
+
+ if (fieldObjA.dictLookup("FT", &obj1)->isName()) {
+ typeStr = new GString(obj1.getName());
+ typeFromParentA = gFalse;
+ } else {
+ typeStr = NULL;
+ typeFromParentA = gTrue;
+ }
+ obj1.free();
+
+ if (fieldObjA.dictLookup("Ff", &obj1)->isInt()) {
+ flagsA = (Guint)obj1.getInt();
+ haveFlags = gTrue;
+ } else {
+ flagsA = 0;
+ haveFlags = gFalse;
+ }
+ obj1.free();
+
+ //----- get info from parent non-terminal fields
+
+ fieldObjA.dictLookup("Parent", &parentObj);
+ depth = 0;
+ while (parentObj.isDict() && depth < maxFieldObjectDepth) {
+
+ if (parentObj.dictLookup("T", &obj1)->isString()) {
+ if (nameA->getLength()) {
+ nameA->insert(0, (Unicode)'.');
+ }
+ nameA->insert(0, obj1.getString());
+ }
+ obj1.free();
+
+ if (!typeStr) {
+ if (parentObj.dictLookup("FT", &obj1)->isName()) {
+ typeStr = new GString(obj1.getName());
+ }
+ obj1.free();
+ }
+
+ if (!haveFlags) {
+ if (parentObj.dictLookup("Ff", &obj1)->isInt()) {
+ flagsA = (Guint)obj1.getInt();
+ haveFlags = gTrue;
+ }
+ obj1.free();
+ }
+
+ parentObj.dictLookup("Parent", &parentObj2);
+ parentObj.free();
+ parentObj = parentObj2;
+
+ ++depth;
+ }
+ parentObj.free();
+
+ if (!typeStr) {
+ error(errSyntaxError, -1, "Missing type in AcroForm field");
+ goto err1;
+ }
+
+ //----- get static XFA info
+
+ xfaFieldA = NULL;
+ if (acroFormA->xfaScanner) {
+ // convert field name to UTF-8, and remove segments that start
+ // with '#' -- to match the XFA field name
+ xfaName = nameA->toUTF8();
+ i0 = 0;
+ while (i0 < xfaName->getLength()) {
+ i1 = i0;
+ while (i1 < xfaName->getLength()) {
+ if (xfaName->getChar(i1) == '.') {
+ ++i1;
+ break;
+ }
+ ++i1;
+ }
+ if (xfaName->getChar(i0) == '#') {
+ xfaName->del(i0, i1 - i0);
+ } else {
+ i0 = i1;
+ }
+ }
+ xfaFieldA = acroFormA->xfaScanner->findField(xfaName);
+ delete xfaName;
+ }
+
+ //----- check for a radio button
+
+ // this is a kludge: if we see a Btn-type field with kids, and the
+ // Ff entry is missing, assume the kids are radio buttons
+ if (typeFromParentA && !typeStr->cmp("Btn") && !haveFlags) {
+ flagsA = acroFormFlagRadio;
+ }
+
+ //----- determine field type
+
+ if (!typeStr->cmp("Btn")) {
+ if (flagsA & acroFormFlagPushbutton) {
+ typeA = acroFormFieldPushbutton;
+ } else if (flagsA & acroFormFlagRadio) {
+ typeA = acroFormFieldRadioButton;
+ } else {
+ typeA = acroFormFieldCheckbox;
+ }
+ } else if (!typeStr->cmp("Tx")) {
+ if (xfaFieldA && xfaFieldA->getBarcodeInfo()) {
+ typeA = acroFormFieldBarcode;
+ } else if (flagsA & acroFormFlagFileSelect) {
+ typeA = acroFormFieldFileSelect;
+ } else if (flagsA & acroFormFlagMultiline) {
+ typeA = acroFormFieldMultilineText;
+ } else {
+ typeA = acroFormFieldText;
+ }
+ } else if (!typeStr->cmp("Ch")) {
+ if (flagsA & acroFormFlagCombo) {
+ typeA = acroFormFieldComboBox;
+ } else {
+ typeA = acroFormFieldListBox;
+ }
+ } else if (!typeStr->cmp("Sig")) {
+ typeA = acroFormFieldSignature;
+ } else {
+ error(errSyntaxError, -1, "Invalid type in AcroForm field");
+ goto err1;
+ }
+ delete typeStr;
+
+ field = new AcroFormField(acroFormA, fieldRefA, &fieldObjA,
+ typeA, nameA, flagsA, typeFromParentA, xfaFieldA);
+ fieldObjA.free();
+ return field;
+
+ err1:
+ delete typeStr;
+ delete nameA;
+ fieldObjA.free();
+ return NULL;
+}
+
+AcroFormField::AcroFormField(AcroForm *acroFormA,
+ Object *fieldRefA, Object *fieldObjA,
+ AcroFormFieldType typeA, TextString *nameA,
+ Guint flagsA, GBool typeFromParentA,
+ XFAField *xfaFieldA) {
+ acroForm = acroFormA;
+ fieldRefA->copy(&fieldRef);
+ fieldObjA->copy(&fieldObj);
+ type = typeA;
+ name = nameA;
+ flags = flagsA;
+ typeFromParent = typeFromParentA;
+ xfaField = xfaFieldA;
+}
+
+AcroFormField::~AcroFormField() {
+ fieldRef.free();
+ fieldObj.free();
+ delete name;
+}
+
+int AcroFormField::getPageNum() {
+ Object kidsObj, annotRef;
+ int pageNum;
+
+ pageNum = 0;
+ if (fieldObj.dictLookup("Kids", &kidsObj)->isArray()) {
+ if (kidsObj.arrayGetLength() > 0) {
+ kidsObj.arrayGetNF(0, &annotRef);
+ pageNum = acroForm->lookupAnnotPage(&annotRef);
+ annotRef.free();
+ }
+ } else {
+ pageNum = acroForm->lookupAnnotPage(&fieldRef);
+ }
+ kidsObj.free();
+ return pageNum;
+}
+
+const char *AcroFormField::getType() {
+ switch (type) {
+ case acroFormFieldPushbutton: return "PushButton";
+ case acroFormFieldRadioButton: return "RadioButton";
+ case acroFormFieldCheckbox: return "Checkbox";
+ case acroFormFieldFileSelect: return "FileSelect";
+ case acroFormFieldMultilineText: return "MultilineText";
+ case acroFormFieldText: return "Text";
+ case acroFormFieldBarcode: return "Barcode";
+ case acroFormFieldComboBox: return "ComboBox";
+ case acroFormFieldListBox: return "ListBox";
+ case acroFormFieldSignature: return "Signature";
+ default: return NULL;
+ }
+}
+
+Unicode *AcroFormField::getName(int *length) {
+ Unicode *u, *ret;
+ int n;
+
+ u = name->getUnicode();
+ n = name->getLength();
+ ret = (Unicode *)gmallocn(n, sizeof(Unicode));
+ memcpy(ret, u, n * sizeof(Unicode));
+ *length = n;
+ return ret;
+}
+
+Unicode *AcroFormField::getValue(int *length) {
+ Object obj1, obj2;
+ Unicode *u;
+ char *s;
+ TextString *ts;
+ GString *gs;
+ int n, i;
+
+ u = NULL;
+ *length = 0;
+
+ // if this field has a counterpart in the XFA form, take the value
+ // from the XFA field (NB: an XFA field with no value overrides the
+ // AcroForm value)
+ if (xfaField) {
+ if (xfaField->getValue()) {
+ u = utf8ToUnicode(xfaField->getValue(), length);
+ }
+
+ // no XFA form - take the AcroForm value
+ } else {
+ fieldLookup("V", &obj1);
+ if (obj1.isName()) {
+ s = obj1.getName();
+ n = (int)strlen(s);
+ u = (Unicode *)gmallocn(n, sizeof(Unicode));
+ for (i = 0; i < n; ++i) {
+ u[i] = s[i] & 0xff;
+ }
+ *length = n;
+ } else if (obj1.isString()) {
+ ts = new TextString(obj1.getString());
+ n = ts->getLength();
+ u = (Unicode *)gmallocn(n, sizeof(Unicode));
+ memcpy(u, ts->getUnicode(), n * sizeof(Unicode));
+ *length = n;
+ delete ts;
+ } else if (obj1.isDict()) {
+ obj1.dictLookup("Contents", &obj2);
+ if (obj2.isString()) {
+ gs = obj2.getString();
+ n = gs->getLength();
+ u = (Unicode *)gmallocn(n, sizeof(Unicode));
+ for (i = 0; i < n; ++i) {
+ u[i] = gs->getChar(i) & 0xff;
+ }
+ *length = n;
+ }
+ obj2.free();
+ }
+ obj1.free();
+ }
+
+ return u;
+}
+
+void AcroFormField::getBBox(double *llx, double *lly,
+ double *urx, double *ury) {
+ Object annotObj, rectObj, numObj;
+ double t;
+
+ *llx = *lly = *urx = *ury = 0;
+
+ if (getAnnotObj(&annotObj)->isDict()) {
+ annotObj.dictLookup("Rect", &rectObj);
+ if (rectObj.isArray() && rectObj.arrayGetLength() == 4) {
+ rectObj.arrayGet(0, &numObj);
+ if (numObj.isNum()) {
+ *llx = numObj.getNum();
+ }
+ numObj.free();
+ rectObj.arrayGet(1, &numObj);
+ if (numObj.isNum()) {
+ *lly = numObj.getNum();
+ }
+ numObj.free();
+ rectObj.arrayGet(2, &numObj);
+ if (numObj.isNum()) {
+ *urx = numObj.getNum();
+ }
+ numObj.free();
+ rectObj.arrayGet(3, &numObj);
+ if (numObj.isNum()) {
+ *ury = numObj.getNum();
+ }
+ numObj.free();
+ }
+ rectObj.free();
+ }
+ annotObj.free();
+
+ if (*llx > *urx) {
+ t = *llx; *llx = *urx; *urx = t;
+ }
+ if (*lly > *ury) {
+ t = *lly; *lly = *ury; *ury = t;
+ }
+}
+
+void AcroFormField::getFont(Ref *fontID, double *fontSize) {
+ Object daObj;
+ GList *daToks;
+ char *fontTag;
+ double tfSize, m2, m3;
+ int tfPos, tmPos, i;
+
+ fontID->num = fontID->gen = -1;
+ *fontSize = 0;
+
+ if (fieldLookup("DA", &daObj)->isString()) {
+
+ // parse the default appearance string
+ daToks = tokenize(daObj.getString());
+ tfPos = tmPos = -1;
+ for (i = 2; i < daToks->getLength(); ++i) {
+ if (!((GString *)daToks->get(i))->cmp("Tf")) {
+ tfPos = i - 2;
+ } else if (i >= 6 && !((GString *)daToks->get(i))->cmp("Tm")) {
+ tmPos = i - 6;
+ }
+ }
+
+ // handle the Tf operator
+ if (tfPos >= 0) {
+ fontTag = ((GString *)daToks->get(tfPos))->getCString();
+ if (*fontTag == '/') {
+ ++fontTag;
+ }
+ *fontID = findFontName(fontTag);
+ tfSize = atof(((GString *)daToks->get(tfPos + 1))->getCString());
+ } else {
+ tfSize = 1;
+ }
+
+ // handle the Tm operator
+ if (tmPos >= 0) {
+ // transformed font size = sqrt(m[2]^2 + m[3]^2) * size
+ m2 = atof(((GString *)daToks->get(tfPos + 2))->getCString());
+ m3 = atof(((GString *)daToks->get(tfPos + 3))->getCString());
+ tfSize *= sqrt(m2*m2 + m3*m3);
+ }
+ *fontSize = tfSize;
+
+ deleteGList(daToks, GString);
+ }
+
+ daObj.free();
+}
+
+Ref AcroFormField::findFontName(char *fontTag) {
+ Object drObj, fontDictObj, fontObj, baseFontObj;
+ Ref fontID;
+ GBool found;
+
+ fontID.num = fontID.gen = -1;
+ found = gFalse;
+
+ if (fieldObj.dictLookup("DR", &drObj)->isDict()) {
+ if (drObj.dictLookup("Font", &fontDictObj)->isDict()) {
+ if (fontDictObj.dictLookupNF(fontTag, &fontObj)->isRef()) {
+ fontID = fontObj.getRef();
+ found = gTrue;
+ }
+ fontObj.free();
+ }
+ fontDictObj.free();
+ }
+ drObj.free();
+ if (found) {
+ return fontID;
+ }
+
+ if (acroForm->acroFormObj.dictLookup("DR", &drObj)->isDict()) {
+ if (drObj.dictLookup("Font", &fontDictObj)->isDict()) {
+ if (fontDictObj.dictLookupNF(fontTag, &fontObj)->isRef()) {
+ fontID = fontObj.getRef();
+ }
+ fontObj.free();
+ }
+ fontDictObj.free();
+ }
+ drObj.free();
+ return fontID;
+}
+
+void AcroFormField::getColor(double *red, double *green, double *blue) {
+ Object daObj;
+ GList *daToks;
+ int i;
+
+ *red = *green = *blue = 0;
+
+ if (fieldLookup("DA", &daObj)->isString()) {
+
+ // parse the default appearance string
+ daToks = tokenize(daObj.getString());
+ for (i = 1; i < daToks->getLength(); ++i) {
+
+ // handle the g operator
+ if (!((GString *)daToks->get(i))->cmp("g")) {
+ *red = *green = *blue =
+ atof(((GString *)daToks->get(i - 1))->getCString());
+ break;
+
+ // handle the rg operator
+ } else if (i >= 3 && !((GString *)daToks->get(i))->cmp("rg")) {
+ *red = atof(((GString *)daToks->get(i - 3))->getCString());
+ *green = atof(((GString *)daToks->get(i - 2))->getCString());
+ *blue = atof(((GString *)daToks->get(i - 1))->getCString());
+ break;
+ }
+ }
+
+ deleteGList(daToks, GString);
+ }
+
+ daObj.free();
+}
+
+int AcroFormField::getMaxLen() {
+ Object obj;
+ int len;
+
+ if (fieldLookup("MaxLen", &obj)->isInt()) {
+ len = obj.getInt();
+ } else {
+ len = -1;
+ }
+ obj.free();
+ return len;
+}
+
+void AcroFormField::draw(int pageNum, Gfx *gfx, GBool printing) {
+ Object kidsObj, annotRef, annotObj;
+ int i;
+
+ // find the annotation object(s)
+ if (fieldObj.dictLookup("Kids", &kidsObj)->isArray()) {
+ for (i = 0; i < kidsObj.arrayGetLength(); ++i) {
+ kidsObj.arrayGetNF(i, &annotRef);
+ annotRef.fetch(acroForm->doc->getXRef(), &annotObj);
+ drawAnnot(pageNum, gfx, printing, &annotRef, &annotObj);
+ annotObj.free();
+ annotRef.free();
+ }
+ } else {
+ drawAnnot(pageNum, gfx, printing, &fieldRef, &fieldObj);
+ }
+ kidsObj.free();
+}
+
+void AcroFormField::drawAnnot(int pageNum, Gfx *gfx, GBool printing,
+ Object *annotRef, Object *annotObj) {
+ Object obj1, obj2;
+ double xMin, yMin, xMax, yMax, t;
+ int annotFlags;
+ GBool oc, render;
+
+ if (!annotObj->isDict()) {
+ return;
+ }
+
+ //----- get the page number
+
+ // the "P" (page) field in annotations is optional, so we can't
+ // depend on it here
+ if (acroForm->lookupAnnotPage(annotRef) != pageNum) {
+ return;
+ }
+
+ //----- check annotation flags
+
+ if (annotObj->dictLookup("F", &obj1)->isInt()) {
+ annotFlags = obj1.getInt();
+ } else {
+ annotFlags = 0;
+ }
+ obj1.free();
+ if ((annotFlags & annotFlagHidden) ||
+ (printing && !(annotFlags & annotFlagPrint)) ||
+ (!printing && (annotFlags & annotFlagNoView))) {
+ return;
+ }
+
+ //----- check the optional content entry
+
+ annotObj->dictLookupNF("OC", &obj1);
+ if (acroForm->doc->getOptionalContent()->evalOCObject(&obj1, &oc) && !oc) {
+ obj1.free();
+ return;
+ }
+ obj1.free();
+
+ //----- get the bounding box
+
+ if (annotObj->dictLookup("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");
+ obj1.free();
+ return;
+ }
+ obj1.free();
+
+ //----- draw it
+
+ render = gFalse;
+ if (acroForm->needAppearances) {
+ render = gTrue;
+ } else if (xfaField && xfaField->getValue()) {
+ render = gTrue;
+ } else {
+ if (!annotObj->dictLookup("AP", &obj1)->isDict()) {
+ render = gTrue;
+ }
+ obj1.free();
+ }
+ if (render) {
+ drawNewAppearance(gfx, annotObj->getDict(),
+ xMin, yMin, xMax, yMax);
+ } else {
+ drawExistingAppearance(gfx, annotObj->getDict(),
+ xMin, yMin, xMax, yMax);
+ }
+}
+
+// Draw the existing appearance stream for a single annotation
+// attached to this field.
+void AcroFormField::drawExistingAppearance(Gfx *gfx, Dict *annot,
+ double xMin, double yMin,
+ double xMax, double yMax) {
+ Object apObj, asObj, appearance, obj1;
+
+ //----- get the appearance stream
+
+ if (annot->lookup("AP", &apObj)->isDict()) {
+ apObj.dictLookup("N", &obj1);
+ if (obj1.isDict()) {
+ if (annot->lookup("AS", &asObj)->isName()) {
+ obj1.dictLookupNF(asObj.getName(), &appearance);
+ } else if (obj1.dictGetLength() == 1) {
+ obj1.dictGetValNF(0, &appearance);
+ } else {
+ obj1.dictLookupNF("Off", &appearance);
+ }
+ asObj.free();
+ } else {
+ apObj.dictLookupNF("N", &appearance);
+ }
+ obj1.free();
+ }
+ apObj.free();
+
+ //----- draw it
+
+ if (!appearance.isNone()) {
+ gfx->drawAnnot(&appearance, NULL, xMin, yMin, xMax, yMax);
+ appearance.free();
+ }
+}
+
+// Regenerate the appearance for this field, and draw it.
+void AcroFormField::drawNewAppearance(Gfx *gfx, Dict *annot,
+ double xMin, double yMin,
+ double xMax, double yMax) {
+ GString *appearBuf;
+ Object appearance, mkObj, ftObj, appearDict, drObj, apObj, asObj;
+ Object resources, fontResources, defaultFont, gfxStateDict;
+ Object obj1, obj2, obj3, obj4;
+ Dict *mkDict;
+ MemStream *appearStream;
+ GfxFontDict *fontDict;
+ GBool hasCaption;
+ double dx, dy, r;
+ GString *val, *caption, *da;
+ GString **text;
+ GBool done;
+ GBool *selection;
+ AnnotBorderType borderType;
+ double borderWidth;
+ double *borderDash;
+ GString *appearanceState;
+ int borderDashLength, rot, quadding, vAlign, comb, nOptions, topIdx, i;
+
+ appearBuf = new GString();
+#if 0 //~debug
+ appearBuf->appendf("1 1 0 rg 0 0 {0:.4f} {1:.4f} re f\n",
+ xMax - xMin, yMax - yMin);
+#endif
+#if 0 //~debug
+ appearBuf->appendf("1 1 0 RG 0 0 {0:.4f} {1:.4f} re s\n",
+ xMax - xMin, yMax - yMin);
+#endif
+
+ // 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);
+ appearBuf->appendf("0 0 {0:.4f} {1:.4f} re f\n",
+ xMax - xMin, yMax - yMin);
+ }
+ obj1.free();
+ }
+
+ // get the field type
+ fieldLookup("FT", &ftObj);
+
+ // draw the border
+ borderType = annotBorderSolid;
+ borderWidth = 1;
+ borderDash = NULL;
+ borderDashLength = 0;
+ if (annot->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 (annot->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();
+ }
+ }
+ }
+ }
+ obj1.free();
+ if (mkDict) {
+ if (borderWidth > 0) {
+ mkDict->lookup("BC", &obj1);
+ if (!(obj1.isArray() && obj1.arrayGetLength() > 0)) {
+ obj1.free();
+ 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") && (flags & acroFormFlagRadio) && !hasCaption) {
+ r = 0.5 * (dx < dy ? dx : dy);
+ switch (borderType) {
+ case annotBorderDashed:
+ appearBuf->append("[");
+ for (i = 0; i < borderDashLength; ++i) {
+ appearBuf->appendf(" {0:.4f}", borderDash[i]);
+ }
+ appearBuf->append("] 0 d\n");
+ // fall through to the solid case
+ case annotBorderSolid:
+ case annotBorderUnderlined:
+ appearBuf->appendf("{0:.4f} w\n", borderWidth);
+ setColor(obj1.getArray(), gFalse, 0, appearBuf);
+ drawCircle(0.5 * dx, 0.5 * dy, r - 0.5 * borderWidth, "s",
+ appearBuf);
+ break;
+ case annotBorderBeveled:
+ case annotBorderInset:
+ appearBuf->appendf("{0:.4f} w\n", 0.5 * borderWidth);
+ setColor(obj1.getArray(), gFalse, 0, appearBuf);
+ drawCircle(0.5 * dx, 0.5 * dy, r - 0.25 * borderWidth, "s",
+ appearBuf);
+ setColor(obj1.getArray(), gFalse,
+ borderType == annotBorderBeveled ? 1 : -1,
+ appearBuf);
+ drawCircleTopLeft(0.5 * dx, 0.5 * dy, r - 0.75 * borderWidth,
+ appearBuf);
+ setColor(obj1.getArray(), gFalse,
+ borderType == annotBorderBeveled ? -1 : 1,
+ appearBuf);
+ drawCircleBottomRight(0.5 * dx, 0.5 * dy, r - 0.75 * borderWidth,
+ appearBuf);
+ break;
+ }
+
+ } else {
+ switch (borderType) {
+ case annotBorderDashed:
+ appearBuf->append("[");
+ for (i = 0; i < borderDashLength; ++i) {
+ appearBuf->appendf(" {0:.4f}", borderDash[i]);
+ }
+ appearBuf->append("] 0 d\n");
+ // fall through to the solid case
+ case annotBorderSolid:
+ appearBuf->appendf("{0:.4f} w\n", borderWidth);
+ setColor(obj1.getArray(), gFalse, 0, appearBuf);
+ appearBuf->appendf("{0:.4f} {0:.4f} {1:.4f} {2:.4f} re s\n",
+ 0.5 * borderWidth,
+ dx - borderWidth, dy - borderWidth);
+ break;
+ case annotBorderBeveled:
+ case annotBorderInset:
+ setColor(obj1.getArray(), gTrue,
+ borderType == annotBorderBeveled ? 1 : -1,
+ appearBuf);
+ appearBuf->append("0 0 m\n");
+ appearBuf->appendf("0 {0:.4f} l\n", dy);
+ appearBuf->appendf("{0:.4f} {1:.4f} l\n", dx, dy);
+ appearBuf->appendf("{0:.4f} {1:.4f} l\n",
+ dx - borderWidth, dy - borderWidth);
+ appearBuf->appendf("{0:.4f} {1:.4f} l\n",
+ borderWidth, dy - borderWidth);
+ appearBuf->appendf("{0:.4f} {0:.4f} l\n", borderWidth);
+ appearBuf->append("f\n");
+ setColor(obj1.getArray(), gTrue,
+ borderType == annotBorderBeveled ? -1 : 1,
+ appearBuf);
+ appearBuf->append("0 0 m\n");
+ appearBuf->appendf("{0:.4f} 0 l\n", dx);
+ appearBuf->appendf("{0:.4f} {1:.4f} l\n", dx, dy);
+ appearBuf->appendf("{0:.4f} {1:.4f} l\n",
+ dx - borderWidth, dy - borderWidth);
+ appearBuf->appendf("{0:.4f} {1:.4f} l\n",
+ dx - borderWidth, borderWidth);
+ appearBuf->appendf("{0:.4f} {0:.4f} l\n", borderWidth);
+ appearBuf->append("f\n");
+ break;
+ case annotBorderUnderlined:
+ appearBuf->appendf("{0:.4f} w\n", borderWidth);
+ setColor(obj1.getArray(), gFalse, 0, appearBuf);
+ appearBuf->appendf("0 0 m {0:.4f} 0 l s\n", dx);
+ break;
+ }
+
+ // clip to the inside of the border
+ appearBuf->appendf("{0:.4f} {0:.4f} {1:.4f} {2:.4f} re W n\n",
+ borderWidth,
+ dx - 2 * borderWidth, dy - 2 * borderWidth);
+ }
+ }
+ obj1.free();
+ }
+ }
+ gfree(borderDash);
+
+ // get the resource dictionary
+ buildDefaultResourceDict(&drObj);
+
+ // build the font dictionary
+ if (drObj.isDict() && drObj.dictLookup("Font", &obj1)->isDict()) {
+ fontDict = new GfxFontDict(acroForm->doc->getXRef(), NULL, obj1.getDict());
+ } else {
+ fontDict = NULL;
+ }
+ obj1.free();
+
+ // get the default appearance string
+ if (fieldLookup("DA", &obj1)->isString()) {
+ da = obj1.getString()->copy();
+ } else {
+ da = NULL;
+ }
+ obj1.free();
+
+ // get the rotation value
+ rot = 0;
+ if (mkDict) {
+ if (mkDict->lookup("R", &obj1)->isInt()) {
+ rot = obj1.getInt();
+ }
+ obj1.free();
+ }
+
+ // get the appearance state
+ annot->lookup("AP", &apObj);
+ annot->lookup("AS", &asObj);
+ appearanceState = NULL;
+ 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();
+ apObj.free();
+
+ int valueLength;
+ Unicode *value = getValue(&valueLength);
+
+ // draw the field contents
+ if (ftObj.isName("Btn")) {
+ caption = NULL;
+ if (mkDict) {
+ if (mkDict->lookup("CA", &obj1)->isString()) {
+ caption = obj1.getString()->copy();
+ }
+ obj1.free();
+ }
+ // radio button
+ if (flags & acroFormFlagRadio) {
+ //~ Acrobat doesn't draw a caption if there is no AP dict (?)
+ if (value && unicodeStringEqual(value, valueLength,
+ appearanceState->getCString())) {
+ if (caption) {
+ drawText(caption, da, fontDict, gFalse, 0,
+ acroFormQuadCenter, acroFormVAlignMiddleNoDescender,
+ gFalse, gTrue, rot, 0, 0, xMax - xMin, yMax - yMin,
+ borderWidth, gFalse, appearBuf);
+ } else {
+ if (mkDict) {
+ if (mkDict->lookup("BC", &obj2)->isArray() &&
+ obj2.arrayGetLength() > 0) {
+ dx = xMax - xMin;
+ dy = yMax - yMin;
+ setColor(obj2.getArray(), gTrue, 0, appearBuf);
+ drawCircle(0.5 * dx, 0.5 * dy, 0.2 * (dx < dy ? dx : dy), "f",
+ appearBuf);
+ }
+ obj2.free();
+ }
+ }
+ }
+ // pushbutton
+ } else if (flags & acroFormFlagPushbutton) {
+ if (caption) {
+ drawText(caption, da, fontDict, gFalse, 0,
+ acroFormQuadCenter, acroFormVAlignMiddle,
+ gFalse, gFalse, rot, 0, 0, xMax - xMin, yMax - yMin,
+ borderWidth, gFalse, appearBuf);
+ }
+ // checkbox
+ } else {
+ if (value && !(unicodeStringEqual(value, valueLength, "Off") ||
+ unicodeStringEqual(value, valueLength, "No") ||
+ unicodeStringEqual(value, valueLength, "0") ||
+ valueLength == 0)) {
+ if (!caption) {
+ caption = new GString("3"); // ZapfDingbats checkmark
+ }
+ drawText(caption, da, fontDict, gFalse, 0,
+ acroFormQuadCenter, acroFormVAlignMiddleNoDescender,
+ gFalse, gTrue, rot, 0, 0, xMax - xMin, yMax - yMin,
+ borderWidth, gFalse, appearBuf);
+ }
+ }
+ if (caption) {
+ delete caption;
+ }
+ } else if (ftObj.isName("Tx")) {
+ XFAFieldBarcodeInfo *barcodeInfo = xfaField ? xfaField->getBarcodeInfo()
+ : (XFAFieldBarcodeInfo *)NULL;
+ if (value) {
+ //~ value strings can be Unicode
+ GString *valueLatin1 = unicodeToLatin1(value, valueLength);
+ if (barcodeInfo) {
+ drawBarcode(valueLatin1, da, fontDict, rot, xMin, yMin, xMax, yMax,
+ barcodeInfo, appearBuf);
+ } else {
+ if (fieldLookup("Q", &obj2)->isInt()) {
+ quadding = obj2.getInt();
+ } else {
+ quadding = acroFormQuadLeft;
+ }
+ obj2.free();
+ vAlign = (flags & acroFormFlagMultiline) ? acroFormVAlignTop
+ : acroFormVAlignMiddle;
+ XFAFieldLayoutInfo *layoutInfo = xfaField ? xfaField->getLayoutInfo()
+ : (XFAFieldLayoutInfo *)NULL;
+ if (layoutInfo) {
+ switch (layoutInfo->hAlign) {
+ case xfaFieldLayoutHAlignLeft:
+ default:
+ quadding = acroFormQuadLeft;
+ break;
+ case xfaFieldLayoutHAlignCenter:
+ quadding = acroFormQuadCenter;
+ break;
+ case xfaFieldLayoutHAlignRight:
+ quadding = acroFormQuadRight;
+ break;
+ }
+ switch (layoutInfo->vAlign) {
+ case xfaFieldLayoutVAlignTop:
+ default:
+ vAlign = acroFormVAlignTop;
+ break;
+ case xfaFieldLayoutVAlignMiddle:
+ vAlign = acroFormVAlignMiddle;
+ break;
+ case xfaFieldLayoutVAlignBottom:
+ vAlign = acroFormVAlignBottom;
+ break;
+ }
+ }
+ comb = 0;
+ if (flags & acroFormFlagComb) {
+ if (fieldLookup("MaxLen", &obj2)->isInt()) {
+ comb = obj2.getInt();
+ }
+ obj2.free();
+ }
+ XFAFieldPictureInfo *pictureInfo =
+ xfaField ? xfaField->getPictureInfo()
+ : (XFAFieldPictureInfo *)NULL;
+ GString *value2 = valueLatin1;
+ if (pictureInfo) {
+ switch (pictureInfo->subtype) {
+ case xfaFieldPictureDateTime:
+ value2 = pictureFormatDateTime(valueLatin1, pictureInfo->format);
+ break;
+ case xfaFieldPictureNumeric:
+ value2 = pictureFormatNumber(valueLatin1, pictureInfo->format);
+ break;
+ case xfaFieldPictureText:
+ value2 = pictureFormatText(valueLatin1, pictureInfo->format);
+ break;
+ }
+ }
+ drawText(value2, da, fontDict,
+ flags & acroFormFlagMultiline, comb, quadding, vAlign,
+ gTrue, gFalse, rot, 0, 0, xMax - xMin, yMax - yMin,
+ borderWidth, gFalse, appearBuf);
+ if (value2 != valueLatin1) {
+ delete value2;
+ }
+ }
+ delete valueLatin1;
+ }
+ } else if (ftObj.isName("Ch")) {
+ //~ value/option strings can be Unicode
+ if (fieldLookup("Q", &obj1)->isInt()) {
+ quadding = obj1.getInt();
+ } else {
+ quadding = acroFormQuadLeft;
+ }
+ obj1.free();
+ vAlign = acroFormVAlignMiddle;
+ XFAFieldLayoutInfo *layoutInfo = xfaField ? xfaField->getLayoutInfo()
+ : (XFAFieldLayoutInfo *)NULL;
+ if (layoutInfo) {
+ switch (layoutInfo->hAlign) {
+ case xfaFieldLayoutHAlignLeft:
+ default:
+ quadding = acroFormQuadLeft;
+ break;
+ case xfaFieldLayoutHAlignCenter:
+ quadding = acroFormQuadCenter;
+ break;
+ case xfaFieldLayoutHAlignRight:
+ quadding = acroFormQuadRight;
+ break;
+ }
+ switch (layoutInfo->vAlign) {
+ case xfaFieldLayoutVAlignTop:
+ default:
+ vAlign = acroFormVAlignTop;
+ break;
+ case xfaFieldLayoutVAlignMiddle:
+ vAlign = acroFormVAlignMiddle;
+ break;
+ case xfaFieldLayoutVAlignBottom:
+ vAlign = acroFormVAlignBottom;
+ break;
+ }
+ }
+ // combo box
+ if (flags & acroFormFlagCombo) {
+ if (value) {
+ val = unicodeToLatin1(value, valueLength);
+ if (fieldObj.dictLookup("Opt", &obj2)->isArray()) {
+ for (i = 0, done = false; i < obj2.arrayGetLength() && !done; ++i) {
+ obj2.arrayGet(i, &obj3);
+ if (obj3.isArray() && obj3.arrayGetLength() == 2) {
+ if (obj3.arrayGet(0, &obj4)->isString() &&
+ obj4.getString()->cmp(val) == 0) {
+ obj4.free();
+ if (obj3.arrayGet(1, &obj4)->isString()) {
+ delete val;
+ val = obj4.getString()->copy();
+ }
+ done = gTrue;
+ }
+ obj4.free();
+ }
+ obj3.free();
+ }
+ }
+ obj2.free();
+ drawText(val, da, fontDict,
+ gFalse, 0, quadding, vAlign, gTrue, gFalse, rot,
+ 0, 0, xMax - xMin, yMax - yMin, borderWidth,
+ gFalse, appearBuf);
+ delete val;
+ //~ Acrobat draws a popup icon on the right side
+ }
+ // list box
+ } else {
+ if (fieldObj.dictLookup("Opt", &obj1)->isArray()) {
+ nOptions = obj1.arrayGetLength();
+ // get the option text
+ text = (GString **)gmallocn(nOptions, sizeof(GString *));
+ 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 GString();
+ }
+ }
+ // get the selected option(s)
+ selection = (GBool *)gmallocn(nOptions, sizeof(GBool));
+ //~ need to use the I field in addition to the V field
+ for (i = 0; i < nOptions; ++i) {
+ selection[i] = unicodeStringEqual(value, valueLength, text[i]);
+ }
+ // get the top index
+ if (fieldObj.dictLookup("TI", &obj2)->isInt()) {
+ topIdx = obj2.getInt();
+ if (topIdx < 0 || topIdx >= nOptions) {
+ topIdx = 0;
+ }
+ } else {
+ topIdx = 0;
+ }
+ obj2.free();
+ // draw the text
+ drawListBox(text, selection, nOptions, topIdx, da, fontDict, quadding,
+ xMin, yMin, xMax, yMax, borderWidth, appearBuf);
+ for (i = 0; i < nOptions; ++i) {
+ delete text[i];
+ }
+ gfree(text);
+ gfree(selection);
+ }
+ obj1.free();
+ }
+ } else if (ftObj.isName("Sig")) {
+ //~ check to see if background is already drawn
+ gfxStateDict.initDict(acroForm->doc->getXRef());
+ obj1.initReal(0.5);
+ gfxStateDict.dictAdd(copyString("ca"), &obj1);
+ appearBuf->append("/GS1 gs\n");
+ appearBuf->appendf("0.7 0.7 1 rg 0 0 {0:.2f} {1:.2f} re f\n",
+ xMax - xMin, yMax - yMin);
+ caption = new GString("SIGN HERE");
+ if (da) {
+ delete da;
+ }
+ da = new GString("/Helv 10 Tf 1 0 0 rg");
+ drawText(caption, da, fontDict, gFalse, 0,
+ acroFormQuadLeft, acroFormVAlignMiddle, gFalse, gFalse, rot,
+ 0, 0, xMax - xMin, yMax - yMin, borderWidth, gFalse, appearBuf);
+ delete caption;
+ } else {
+ error(errSyntaxError, -1, "Unknown field type");
+ }
+
+ gfree(value);
+
+ delete appearanceState;
+ if (da) {
+ delete da;
+ }
+
+ // build the appearance stream dictionary
+ appearDict.initDict(acroForm->doc->getXRef());
+ appearDict.dictAdd(copyString("Length"),
+ obj1.initInt(appearBuf->getLength()));
+ appearDict.dictAdd(copyString("Subtype"), obj1.initName("Form"));
+ obj1.initArray(acroForm->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);
+
+ // set the resource dictionary; add a default font
+ if (drObj.isDict()) {
+ drObj.copy(&resources);
+ } else {
+ resources.initDict(acroForm->doc->getXRef());
+ }
+ drObj.free();
+ fontResources.initDict(acroForm->doc->getXRef());
+ if (resources.dictLookup("Font", &obj1)->isDict()) {
+ for (i = 0; i < obj1.dictGetLength(); ++i) {
+ obj1.dictGetValNF(i, &obj2);
+ fontResources.dictAdd(copyString(obj1.dictGetKey(i)), &obj2);
+ }
+ }
+ obj1.free();
+ defaultFont.initDict(acroForm->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.dictAdd(copyString("xpdf_default_font"), &defaultFont);
+ resources.dictAdd(copyString("Font"), &fontResources);
+ if (gfxStateDict.isDict()) {
+ obj1.initDict(acroForm->doc->getXRef());
+ obj1.dictAdd(copyString("GS1"), &gfxStateDict);
+ resources.dictAdd(copyString("ExtGState"), &obj1);
+ }
+ appearDict.dictAdd(copyString("Resources"), &resources);
+
+ // build the appearance stream
+ appearStream = new MemStream(appearBuf->getCString(), 0,
+ appearBuf->getLength(), &appearDict);
+ appearance.initStream(appearStream);
+
+ // draw it
+ gfx->drawAnnot(&appearance, NULL, xMin, yMin, xMax, yMax);
+
+ appearance.free();
+ delete appearBuf;
+ appearBuf = NULL;
+ if (fontDict) {
+ delete fontDict;
+ }
+ ftObj.free();
+ mkObj.free();
+}
+
+// Set the current fill or stroke color, based on <a> (which should
+// have 1, 3, or 4 elements). If <adjust> is +1, color is brightened;
+// if <adjust> is -1, color is darkened; otherwise color is not
+// modified.
+void AcroFormField::setColor(Array *a, GBool fill, int adjust,
+ GString *appearBuf) {
+ 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');
+ }
+}
+
+// Draw the variable text or caption for a field.
+void AcroFormField::drawText(GString *text, GString *da, GfxFontDict *fontDict,
+ GBool multiline, int comb,
+ int quadding, int vAlign,
+ GBool txField, GBool forceZapfDingbats, int rot,
+ double x, double y, double width, double height,
+ double border, GBool whiteBackground,
+ GString *appearBuf) {
+ GString *text2;
+ GList *daToks;
+ GString *tok;
+ GfxFont *font;
+ double dx, dy;
+ double fontSize, fontSize2, topBorder, xx, xPrev, yy, w, wMax;
+ double offset, offset2, charWidth, ascent, descent;
+ int tfPos, tmPos, nLines, 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)
+
+ // 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;
+ }
+ if (text2->getLength() == 0) {
+ if (text2 != text) {
+ delete text2;
+ }
+ return;
+ }
+
+ // parse the default appearance string
+ tfPos = tmPos = -1;
+ if (da) {
+ daToks = tokenize(da);
+ for (i = 2; i < daToks->getLength(); ++i) {
+ if (!((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;
+ }
+
+ // force ZapfDingbats
+ //~ this should create the font if needed (?)
+ if (forceZapfDingbats) {
+ if (tfPos >= 0) {
+ tok = (GString *)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 = (GString *)daToks->get(tfPos);
+ if (tok->getLength() >= 1 && tok->getChar(0) == '/') {
+ if (!fontDict || !(font = fontDict->lookup(tok->getCString() + 1))) {
+ error(errSyntaxError, -1, "Unknown font in field's DA string");
+ tok->clear();
+ tok->append("/xpdf_default_font");
+ }
+ } else {
+ error(errSyntaxError, -1,
+ "Invalid font name in 'Tf' operator in field's DA string");
+ }
+ tok = (GString *)daToks->get(tfPos + 1);
+ fontSize = atof(tok->getCString());
+ } else {
+ error(errSyntaxError, -1, "Missing 'Tf' operator in field's DA string");
+ fontSize = 0;
+ if (!daToks) {
+ daToks = new GList();
+ }
+ tfPos = daToks->getLength();
+ daToks->append(new GString("/xpdf_default_font"));
+ daToks->append(new GString("10"));
+ daToks->append(new GString("Tf"));
+ }
+
+ // setup
+ if (txField) {
+ appearBuf->append("/Tx BMC\n");
+ }
+ appearBuf->append("q\n");
+ if (rot == 90) {
+ appearBuf->appendf("0 1 -1 0 {0:.4f} 0 cm\n", width);
+ dx = height;
+ dy = width;
+ } else if (rot == 180) {
+ appearBuf->appendf("-1 0 0 -1 {0:.4f} {1:.4f} cm\n", width, height);
+ dx = width;
+ dy = height;
+ } else if (rot == 270) {
+ appearBuf->appendf("0 -1 1 0 0 {0:.4f} cm\n", height);
+ dx = height;
+ dy = width;
+ } else { // assume rot == 0
+ dx = width;
+ dy = height;
+ }
+
+ // multi-line text
+ if (multiline) {
+ // note: the comb flag is ignored in multiline mode
+
+ wMax = dx - 2 * border - 4;
+
+#if 1 //~tmp
+ // this is a kludge that appears to match Adobe's behavior
+ if (height > 15) {
+ topBorder = 5;
+ } else {
+ topBorder = 2;
+ }
+#else
+ topBorder = 5;
+#endif
+
+ // compute font autosize
+ if (fontSize == 0) {
+ for (fontSize = 10; fontSize > 1; --fontSize) {
+ yy = dy - topBorder;
+ i = 0;
+ while (i < text2->getLength()) {
+ getNextLine(text2, i, font, fontSize, wMax, &j, &w, &k);
+ i = k;
+ yy -= fontSize;
+ }
+ // approximate the descender for the last line
+ if (yy >= 0.25 * fontSize && w <= wMax) {
+ break;
+ }
+ }
+ if (tfPos >= 0) {
+ tok = (GString *)daToks->get(tfPos + 1);
+ tok->clear();
+ tok->appendf("{0:.2f}", fontSize);
+ }
+ }
+
+ // starting y coordinate
+ nLines = 0;
+ i = 0;
+ while (i < text2->getLength()) {
+ getNextLine(text2, i, font, fontSize, wMax, &j, &w, &k);
+ i = k;
+ ++nLines;
+ }
+ if (font) {
+ ascent = font->getDeclaredAscent() * fontSize;
+ descent = font->getDescent() * fontSize;
+ } else {
+ ascent = 0.75 * fontSize;
+ descent = -0.25 * fontSize;
+ }
+ switch (vAlign) {
+ case acroFormVAlignTop:
+ default:
+ yy = dy - ascent - topBorder;
+ break;
+ case acroFormVAlignMiddle:
+ yy = 0.5 * (dy - nLines * fontSize) + (nLines - 1) * fontSize - descent;
+ break;
+ case acroFormVAlignMiddleNoDescender:
+ yy = 0.5 * (dy - nLines * fontSize) + (nLines - 1) * fontSize;
+ break;
+ case acroFormVAlignBottom:
+ yy = (nLines - 1) * fontSize - descent;
+ break;
+ }
+ // if the field is shorter than a line of text, Acrobat positions
+ // the text relative to the bottom edge
+ if (dy < fontSize + topBorder) {
+ yy = 2 - descent;
+ }
+ // each line of text starts with a Td operator that moves down a
+ // line -- so move up a line here
+ yy += fontSize;
+
+ appearBuf->append("BT\n");
+
+ // 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 + yy);
+ }
+
+ // 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 + yy);
+ }
+
+ // write a series of lines of text
+ i = 0;
+ xPrev = 0;
+ while (i < text2->getLength()) {
+
+ getNextLine(text2, i, font, fontSize, wMax, &j, &w, &k);
+
+ // compute text start position
+ switch (quadding) {
+ case acroFormQuadLeft:
+ default:
+ xx = border + 2;
+ break;
+ case acroFormQuadCenter:
+ xx = (dx - w) / 2;
+ break;
+ case acroFormQuadRight:
+ xx = dx - border - 2 - w;
+ break;
+ }
+
+ // draw the line
+ appearBuf->appendf("{0:.4f} {1:.4f} Td\n", xx - xPrev, -fontSize);
+ appearBuf->append('(');
+ for (; i < j; ++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");
+
+ // next line
+ i = k;
+ xPrev = xx;
+ }
+
+ appearBuf->append("ET\n");
+
+ // single-line text
+ } else {
+ //~ replace newlines with spaces? - what does Acrobat do?
+
+ // comb formatting
+ if (comb > 0) {
+
+ // compute comb spacing
+ w = dx / comb;
+
+ // compute font autosize
+ if (fontSize == 0) {
+ fontSize = dy - 2 * border;
+ if (w < fontSize) {
+ fontSize = w;
+ }
+ fontSize = floor(fontSize);
+ if (fontSize > 10) {
+ fontSize = 10;
+ }
+ if (tfPos >= 0) {
+ tok = (GString *)daToks->get(tfPos + 1);
+ tok->clear();
+ tok->appendf("{0:.4f}", fontSize);
+ }
+ }
+
+ // compute text start position
+ switch (quadding) {
+ case acroFormQuadLeft:
+ default:
+ xx = 0;
+ break;
+ case acroFormQuadCenter:
+ xx = ((comb - text2->getLength()) / 2) * w;
+ break;
+ case acroFormQuadRight:
+ xx = (comb - text2->getLength()) * w;
+ break;
+ }
+ if (font) {
+ ascent = font->getDeclaredAscent() * fontSize;
+ descent = font->getDescent() * fontSize;
+ } else {
+ ascent = 0.75 * fontSize;
+ descent = -0.25 * fontSize;
+ }
+ switch (vAlign) {
+ case acroFormVAlignTop:
+ default:
+ yy = dy - ascent;
+ break;
+ case acroFormVAlignMiddle:
+ yy = 0.5 * (dy - ascent - descent);
+ break;
+ case acroFormVAlignMiddleNoDescender:
+ yy = 0.5 * (dy - ascent);
+ break;
+ case acroFormVAlignBottom:
+ yy = -descent;
+ break;
+ }
+
+ appearBuf->append("BT\n");
+
+ // set the font matrix
+ if (tmPos >= 0) {
+ tok = (GString *)daToks->get(tmPos + 4);
+ tok->clear();
+ tok->appendf("{0:.4f}", x + xx);
+ tok = (GString *)daToks->get(tmPos + 5);
+ tok->clear();
+ tok->appendf("{0:.4f}", y + yy);
+ }
+
+ // 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 + xx, y + yy);
+ }
+
+ // write the text string
+ offset = 0;
+ for (i = 0; i < text2->getLength(); ++i) {
+ c = text2->getChar(i) & 0xff;
+ if (c >= 0x20 && c < 0x80) {
+ if (font && !font->isCIDFont()) {
+ charWidth = ((Gfx8BitFont *)font)->getWidth((Guchar)c) * fontSize;
+ } else {
+ // otherwise, make a crude estimate
+ charWidth = 0.5 * fontSize;
+ }
+ offset2 = 0.5 * (w - charWidth);
+ appearBuf->appendf("{0:.4f} 0 Td\n", offset + offset2);
+ if (c == '(' || c == ')' || c == '\\') {
+ appearBuf->appendf("(\\{0:c}) Tj\n", c);
+ } else {
+ appearBuf->appendf("({0:c}) Tj\n", c);
+ }
+ offset = w - offset2;
+ } else {
+ offset += w;
+ }
+ }
+
+ appearBuf->append("ET\n");
+
+ // regular (non-comb) formatting
+ } else {
+
+ // compute string width
+ if (font && !font->isCIDFont()) {
+ w = 0;
+ for (i = 0; i < text2->getLength(); ++i) {
+ w += ((Gfx8BitFont *)font)->getWidth(text2->getChar(i));
+ }
+ } else {
+ // otherwise, make a crude estimate
+ w = text2->getLength() * 0.5;
+ }
+
+ // compute font autosize
+ if (fontSize == 0) {
+ fontSize = dy - 2 * border;
+ fontSize2 = (dx - 4 - 2 * border) / w;
+ if (fontSize2 < fontSize) {
+ fontSize = fontSize2;
+ }
+ fontSize = floor(fontSize);
+ if (fontSize > 10) {
+ fontSize = 10;
+ }
+ 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 acroFormQuadLeft:
+ default:
+ xx = border + 2;
+ break;
+ case acroFormQuadCenter:
+ xx = (dx - w) / 2;
+ break;
+ case acroFormQuadRight:
+ xx = dx - border - 2 - w;
+ break;
+ }
+ if (font) {
+ ascent = font->getDeclaredAscent() * fontSize;
+ descent = font->getDescent() * fontSize;
+ } else {
+ ascent = 0.75 * fontSize;
+ descent = -0.25 * fontSize;
+ }
+ switch (vAlign) {
+ case acroFormVAlignTop:
+ default:
+ yy = dy - ascent;
+ break;
+ case acroFormVAlignMiddle:
+ yy = 0.5 * (dy - ascent - descent);
+ break;
+ case acroFormVAlignMiddleNoDescender:
+ yy = 0.5 * (dy - ascent);
+ break;
+ case acroFormVAlignBottom:
+ yy = -descent;
+ break;
+ }
+
+ if (whiteBackground) {
+ appearBuf->appendf("q 1 g {0:.4f} {1:.4f} {2:.4f} {3:.4f} re f Q\n",
+ xx - 0.25 * fontSize, yy - 0.35 * fontSize,
+ w + 0.5 * fontSize, 1.2 * fontSize);
+ }
+
+ appearBuf->append("BT\n");
+
+ // set the font matrix
+ if (tmPos >= 0) {
+ tok = (GString *)daToks->get(tmPos + 4);
+ tok->clear();
+ tok->appendf("{0:.4f}", x + xx);
+ tok = (GString *)daToks->get(tmPos + 5);
+ tok->clear();
+ tok->appendf("{0:.4f}", y + yy);
+ }
+
+ // 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 + xx, y + yy);
+ }
+
+ // 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");
+ }
+
+ appearBuf->append("ET\n");
+ }
+
+ // cleanup
+ appearBuf->append("Q\n");
+ if (txField) {
+ appearBuf->append("EMC\n");
+ }
+
+ if (daToks) {
+ deleteGList(daToks, GString);
+ }
+ if (text2 != text) {
+ delete text2;
+ }
+}
+
+// Draw the variable text or caption for a field.
+void AcroFormField::drawListBox(GString **text, GBool *selection,
+ int nOptions, int topIdx,
+ GString *da, GfxFontDict *fontDict,
+ GBool quadding, double xMin, double yMin,
+ double xMax, double yMax, double border,
+ GString *appearBuf) {
+ GList *daToks;
+ GString *tok;
+ GfxFont *font;
+ double fontSize, fontSize2, 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) {
+ daToks = tokenize(da);
+ 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
+ font = NULL;
+ fontSize = 0;
+ if (tfPos >= 0) {
+ tok = (GString *)daToks->get(tfPos);
+ if (tok->getLength() >= 1 && tok->getChar(0) == '/') {
+ if (!fontDict || !(font = fontDict->lookup(tok->getCString() + 1))) {
+ error(errSyntaxError, -1, "Unknown font in field's DA string");
+ tok->clear();
+ tok->append("/xpdf_default_font");
+ }
+ } else {
+ error(errSyntaxError, -1,
+ "Invalid font name in 'Tf' operator in field's DA string");
+ }
+ tok = (GString *)daToks->get(tfPos + 1);
+ fontSize = atof(tok->getCString());
+ } else {
+ error(errSyntaxError, -1, "Missing 'Tf' operator in field's DA string");
+ }
+
+ // 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 = yMax - yMin - 2 * border;
+ fontSize2 = (xMax - xMin - 4 - 2 * border) / wMax;
+ if (fontSize2 < fontSize) {
+ fontSize = fontSize2;
+ }
+ fontSize = floor(fontSize);
+ if (fontSize > 10) {
+ fontSize = 10;
+ }
+ if (tfPos >= 0) {
+ tok = (GString *)daToks->get(tfPos + 1);
+ tok->clear();
+ tok->appendf("{0:.4f}", fontSize);
+ }
+ }
+
+ // draw the text
+ y = yMax - yMin - 1.1 * fontSize;
+ for (i = topIdx; i < nOptions; ++i) {
+
+ // setup
+ appearBuf->append("q\n");
+
+ // draw the background if selected
+ if (selection[i]) {
+ appearBuf->append("0 g f\n");
+ appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} re f\n",
+ border,
+ y - 0.2 * fontSize,
+ xMax - xMin - 2 * border,
+ 1.1 * 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;
+ }
+
+ // compute text start position
+ w *= fontSize;
+ switch (quadding) {
+ case acroFormQuadLeft:
+ default:
+ x = border + 2;
+ break;
+ case acroFormQuadCenter:
+ x = (xMax - xMin - w) / 2;
+ break;
+ case acroFormQuadRight:
+ x = xMax - xMin - border - 2 - w;
+ break;
+ }
+
+ // 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 (j = 0; j < daToks->getLength(); ++j) {
+ appearBuf->append((GString *)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:.4f} {1:.4f} Tm\n", x, y);
+ }
+
+ // change the text color if selected
+ if (selection[i]) {
+ appearBuf->append("1 g\n");
+ }
+
+ // write the text string
+ appearBuf->append('(');
+ for (j = 0; j < text[i]->getLength(); ++j) {
+ c = text[i]->getChar(j) & 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");
+
+ // next line
+ y -= 1.1 * fontSize;
+ }
+
+ if (daToks) {
+ deleteGList(daToks, GString);
+ }
+}
+
+// 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 AcroFormField::getNextLine(GString *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((Guchar)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 <r> centered at (<cx>, <cy>).
+// <cmd> is used to draw the circle ("f", "s", or "b").
+void AcroFormField::drawCircle(double cx, double cy, double r,
+ const char *cmd, GString *appearBuf) {
+ 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 AcroFormField::drawCircleTopLeft(double cx, double cy, double r,
+ GString *appearBuf) {
+ 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 AcroFormField::drawCircleBottomRight(double cx, double cy, double r,
+ GString *appearBuf) {
+ 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 AcroFormField::drawBarcode(GString *value, GString *da,
+ GfxFontDict *fontDict, int rot,
+ double xMin, double yMin,
+ double xMax, double yMax,
+ XFAFieldBarcodeInfo *barcodeInfo,
+ GString *appearBuf) {
+ //--- handle rotation
+ double w, h;
+ appearBuf->append("q\n");
+ switch (rot) {
+ case 0:
+ default:
+ w = xMax - xMin;
+ h = yMax - yMin;
+ break;
+ case 90:
+ appearBuf->appendf("0 1 -1 0 {0:.4f} 0 cm\n", xMax - xMin);
+ w = yMax - yMin;
+ h = xMax - xMin;
+ break;
+ case 180:
+ appearBuf->appendf("0 -1 1 0 0 {0:.4f} cm\n", yMax - yMin);
+ w = yMax - yMin;
+ h = xMax - xMin;
+ break;
+ case 270:
+ appearBuf->appendf("0 -1 1 0 0 {0:.4f} cm\n", yMax - yMin);
+ w = yMax - yMin;
+ h = xMax - xMin;
+ break;
+ }
+
+ //--- get the font size
+ double fontSize = 0.2 * h;
+ if (da) {
+ GList *daToks = tokenize(da);
+ for (int i = 2; i < daToks->getLength(); ++i) {
+ if (!((GString *)daToks->get(i))->cmp("Tf")) {
+ fontSize = atof(((GString *)daToks->get(i - 1))->getCString());
+ break;
+ }
+ }
+ deleteGList(daToks, GString);
+ }
+
+ //--- compute the embedded text type position
+ GBool doText = gTrue;
+ double yText = 0;
+ int vAlign = acroFormVAlignTop;
+ double yBarcode = 0;
+ double hBarcode = 0;
+ GBool whiteBackground = gFalse;
+ //~ this uses an estimate of the font baseline position
+ if (barcodeInfo->textLocation &&
+ !barcodeInfo->textLocation->cmp("above")) {
+ yText = h;
+ vAlign = acroFormVAlignTop;
+ yBarcode = 0;
+ hBarcode = h - fontSize;
+ } else if (barcodeInfo->textLocation &&
+ !barcodeInfo->textLocation->cmp("belowEmbedded")) {
+ yText = 0;
+ vAlign = acroFormVAlignBottom;
+ yBarcode = 0;
+ hBarcode = h;
+ whiteBackground = gTrue;
+ } else if (barcodeInfo->textLocation &&
+ !barcodeInfo->textLocation->cmp("aboveEmbedded")) {
+ yText = h;
+ vAlign = acroFormVAlignTop;
+ yBarcode = 0;
+ hBarcode = h;
+ whiteBackground = gTrue;
+ } else if (barcodeInfo->textLocation &&
+ !barcodeInfo->textLocation->cmp("none")) {
+ doText = gFalse;
+ } else { // default is "below"
+ yText = 0;
+ vAlign = acroFormVAlignBottom;
+ yBarcode = fontSize;
+ hBarcode = h - fontSize;
+ }
+ double wText = w;
+
+ //--- remove extraneous start/stop chars
+ GString *value2 = value->copy();
+ if (!barcodeInfo->barcodeType->cmp("code3Of9")) {
+ if (value2->getLength() >= 1 && value2->getChar(0) == '*') {
+ value2->del(0);
+ }
+ if (value2->getLength() >= 1 &&
+ value2->getChar(value2->getLength() - 1) == '*') {
+ value2->del(value2->getLength() - 1);
+ }
+ }
+
+ //--- draw the bar code
+ if (!barcodeInfo->barcodeType->cmp("code3Of9")) {
+ if (!barcodeInfo->dataLength) {
+ error(errSyntaxError, -1,
+ "Missing 'dataLength' attribute in barcode field");
+ goto err;
+ }
+ appearBuf->append("0 g\n");
+ double wNarrow = w / ((7 + 3 * barcodeInfo->wideNarrowRatio)
+ * (barcodeInfo->dataLength + 2));
+ double xx = 0;
+ for (int i = -1; i <= value2->getLength(); ++i) {
+ int c;
+ if (i < 0 || i >= value2->getLength()) {
+ c = '*';
+ } else {
+ c = value2->getChar(i) & 0x7f;
+ }
+ for (int j = 0; j < 10; j += 2) {
+ appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} re f\n",
+ xx, yBarcode,
+ (code3Of9Data[c][j] ? barcodeInfo->wideNarrowRatio
+ : 1) * wNarrow,
+ hBarcode);
+ xx += ((code3Of9Data[c][j] ? barcodeInfo->wideNarrowRatio : 1) +
+ (code3Of9Data[c][j+1] ? barcodeInfo->wideNarrowRatio : 1))
+ * wNarrow;
+ }
+ }
+ // center the text on the drawn barcode (not the max length barcode)
+ wText = (value2->getLength() + 2) * (7 + 3 * barcodeInfo->wideNarrowRatio)
+ * wNarrow;
+ } else if (!barcodeInfo->barcodeType->cmp("code128B")) {
+ if (!barcodeInfo->dataLength) {
+ error(errSyntaxError, -1,
+ "Missing 'dataLength' attribute in barcode field");
+ goto err;
+ }
+ appearBuf->append("0 g\n");
+ double wNarrow = w / (11 * (barcodeInfo->dataLength + 3) + 2);
+ double xx = 0;
+ int checksum = 0;
+ for (int i = -1; i <= value2->getLength() + 1; ++i) {
+ int c;
+ if (i == -1) {
+ // start code B
+ c = 104;
+ checksum += c;
+ } else if (i == value2->getLength()) {
+ // checksum
+ c = checksum % 103;
+ } else if (i == value2->getLength() + 1) {
+ // stop code
+ c = 106;
+ } else {
+ c = value2->getChar(i) & 0xff;
+ if (c >= 32 && c <= 127) {
+ c -= 32;
+ } else {
+ c = 0;
+ }
+ checksum += (i + 1) * c;
+ }
+ for (int j = 0; j < 6; j += 2) {
+ appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} re f\n",
+ xx, yBarcode,
+ code128Data[c][j] * wNarrow, hBarcode);
+ xx += (code128Data[c][j] + code128Data[c][j+1]) * wNarrow;
+ }
+ }
+ // final bar of the stop code
+ appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} re f\n",
+ xx, yBarcode, 2 * wNarrow, hBarcode);
+ // center the text on the drawn barcode (not the max length barcode)
+ wText = (11 * (value2->getLength() + 3) + 2) * wNarrow;
+ } else if (!barcodeInfo->barcodeType->cmp("pdf417")) {
+ drawPDF417Barcode(w, h, barcodeInfo->moduleWidth,
+ barcodeInfo->moduleHeight,
+ barcodeInfo->errorCorrectionLevel,
+ value2, appearBuf);
+ doText = gFalse;
+ } else {
+ error(errSyntaxError, -1,
+ "Unimplemented barcode type '{0:t}' in barcode field",
+ barcodeInfo->barcodeType);
+ }
+ //~ add other barcode types here
+
+ //--- draw the embedded text
+ if (doText) {
+ drawText(value2, da, fontDict, gFalse, 0, acroFormQuadCenter,
+ vAlign, gFalse, gFalse, 0, 0, yText, wText, yText + fontSize,
+ 0, whiteBackground, appearBuf);
+ }
+
+ appearBuf->append("Q\n");
+
+ err:
+ delete value2;
+}
+
+GList *AcroFormField::tokenize(GString *s) {
+ GList *toks;
+ int i, j;
+
+ toks = new GList();
+ i = 0;
+ while (i < s->getLength()) {
+ while (i < s->getLength() && Lexer::isSpace(s->getChar(i))) {
+ ++i;
+ }
+ if (i < s->getLength()) {
+ for (j = i + 1;
+ j < s->getLength() && !Lexer::isSpace(s->getChar(j));
+ ++j) ;
+ toks->append(new GString(s, i, j - i));
+ i = j;
+ }
+ }
+ return toks;
+}
+
+Object *AcroFormField::getResources(Object *res) {
+ Object kidsObj, annotObj, obj1;
+ int i;
+
+ if (acroForm->needAppearances) {
+ fieldLookup("DR", res);
+ } else {
+ res->initArray(acroForm->doc->getXRef());
+ // find the annotation object(s)
+ if (fieldObj.dictLookup("Kids", &kidsObj)->isArray()) {
+ for (i = 0; i < kidsObj.arrayGetLength(); ++i) {
+ kidsObj.arrayGet(i, &annotObj);
+ if (annotObj.isDict()) {
+ if (getAnnotResources(annotObj.getDict(), &obj1)->isDict()) {
+ res->arrayAdd(&obj1);
+ } else {
+ obj1.free();
+ }
+ }
+ annotObj.free();
+ }
+ } else {
+ if (getAnnotResources(fieldObj.getDict(), &obj1)->isDict()) {
+ res->arrayAdd(&obj1);
+ } else {
+ obj1.free();
+ }
+ }
+ kidsObj.free();
+ }
+
+ return res;
+}
+
+Object *AcroFormField::getFieldRef(Object *ref) {
+ return fieldRef.copy(ref);
+}
+
+Object *AcroFormField::getValueObj(Object *val) {
+ return fieldLookup("V", val);
+}
+
+Object *AcroFormField::getParentRef(Object *parent) {
+ return fieldObj.dictLookupNF("Parent", parent);
+}
+
+// Get the first annotation object associated with this field.
+Object *AcroFormField::getAnnotObj(Object *annotObj) {
+ Object kidsObj;
+
+ if (fieldObj.dictLookup("Kids", &kidsObj)->isArray()) {
+ if (kidsObj.arrayGetLength() > 0) {
+ kidsObj.arrayGet(0, annotObj);
+ } else {
+ annotObj->initNull();
+ }
+ } else {
+ fieldObj.copy(annotObj);
+ }
+ kidsObj.free();
+ return annotObj;
+}
+
+Object *AcroFormField::getAnnotResources(Dict *annot, Object *res) {
+ Object apObj, asObj, appearance, obj1;
+
+ // get the appearance stream
+ if (annot->lookup("AP", &apObj)->isDict()) {
+ apObj.dictLookup("N", &obj1);
+ if (obj1.isDict()) {
+ if (annot->lookup("AS", &asObj)->isName()) {
+ obj1.dictLookup(asObj.getName(), &appearance);
+ } else if (obj1.dictGetLength() == 1) {
+ obj1.dictGetVal(0, &appearance);
+ } else {
+ obj1.dictLookup("Off", &appearance);
+ }
+ asObj.free();
+ } else {
+ obj1.copy(&appearance);
+ }
+ obj1.free();
+ }
+ apObj.free();
+
+ if (appearance.isStream()) {
+ appearance.streamGetDict()->lookup("Resources", res);
+ } else {
+ res->initNull();
+ }
+ appearance.free();
+
+ return res;
+}
+
+// Merge the field and AcroForm DR objects.
+void AcroFormField::buildDefaultResourceDict(Object *dr) {
+ Object formDR, fieldDR, resDict, newResDict, resObj;
+ char *resType, *resName;
+ int i, j;
+
+ // NB: we need to deep-copy the dictionaries here, because multiple
+ // threads can be sharing these objects.
+
+ dr->initDict(acroForm->doc->getXRef());
+
+ acroForm->acroFormObj.dictLookup("DR", &formDR);
+ if (formDR.isDict()) {
+ for (i = 0; i < formDR.dictGetLength(); ++i) {
+ resType = formDR.dictGetKey(i);
+ formDR.dictGetVal(i, &resDict);
+ if (resDict.isDict()) {
+ newResDict.initDict(acroForm->doc->getXRef());
+ dr->dictAdd(copyString(resType), &newResDict);
+ for (j = 0; j < resDict.dictGetLength(); ++j) {
+ resName = resDict.dictGetKey(j);
+ resDict.dictGetValNF(j, &resObj);
+ newResDict.dictAdd(copyString(resName), &resObj);
+ }
+ }
+ resDict.free();
+ }
+ }
+ formDR.free();
+
+ fieldObj.dictLookup("DR", &fieldDR);
+ if (fieldDR.isDict()) {
+ for (i = 0; i < fieldDR.dictGetLength(); ++i) {
+ resType = fieldDR.dictGetKey(i);
+ fieldDR.dictGetVal(i, &resDict);
+ if (resDict.isDict()) {
+ dr->dictLookup(resType, &newResDict);
+ if (!newResDict.isDict()) {
+ newResDict.free();
+ newResDict.initDict(acroForm->doc->getXRef());
+ }
+ dr->dictAdd(copyString(resType), &newResDict);
+ for (j = 0; j < resDict.dictGetLength(); ++j) {
+ resName = resDict.dictGetKey(j);
+ resDict.dictGetValNF(j, &resObj);
+ newResDict.dictAdd(copyString(resName), &resObj);
+ }
+ }
+ resDict.free();
+ }
+ }
+ fieldDR.free();
+}
+
+// Look up an inheritable field dictionary entry.
+Object *AcroFormField::fieldLookup(const char *key, Object *obj) {
+ return fieldLookup(fieldObj.getDict(), key, obj);
+}
+
+Object *AcroFormField::fieldLookup(Dict *dict, const char *key, Object *obj) {
+ Object parent, parent2;
+ int depth;
+
+ if (!dict->lookup(key, obj)->isNull()) {
+ return obj;
+ }
+ obj->free();
+
+ dict->lookup("Parent", &parent)->isDict();
+ depth = 0;
+ while (parent.isDict() && depth < maxFieldObjectDepth) {
+ if (!parent.dictLookup(key, obj)->isNull()) {
+ parent.free();
+ return obj;
+ }
+ obj->free();
+ parent.dictLookup("Parent", &parent2);
+ parent.free();
+ parent = parent2;
+ ++depth;
+ }
+ parent.free();
+
+ // some fields don't specify a parent, so we check the AcroForm
+ // dictionary just in case
+ acroForm->acroFormObj.dictLookup(key, obj);
+ return obj;
+}
+
+Unicode *AcroFormField::utf8ToUnicode(GString *s, int *unicodeLength) {
+ int n = 0;
+ int i = 0;
+ Unicode u;
+ while (getUTF8(s, &i, &u)) {
+ ++n;
+ }
+ Unicode *uVec = (Unicode *)gmallocn(n, sizeof(Unicode));
+ n = 0;
+ i = 0;
+ while (getUTF8(s, &i, &uVec[n])) {
+ ++n;
+ }
+ *unicodeLength = n;
+ return uVec;
+}
+
+GString *AcroFormField::unicodeToLatin1(Unicode *u, int unicodeLength) {
+ GString *s = new GString();
+ for (int i = 0; i < unicodeLength; ++i) {
+ if (u[i] <= 0xff) {
+ s->append((char)u[i]);
+ }
+ }
+ return s;
+}
+
+GBool AcroFormField::unicodeStringEqual(Unicode *u, int unicodeLength,
+ GString *s) {
+ if (s->getLength() != unicodeLength) {
+ return gFalse;
+ }
+ for (int i = 0; i < unicodeLength; ++i) {
+ if ((s->getChar(i) & 0xff) != u[i]) {
+ return gFalse;
+ }
+ }
+ return gTrue;
+}
+
+GBool AcroFormField::unicodeStringEqual(Unicode *u, int unicodeLength,
+ const char *s) {
+ for (int i = 0; i < unicodeLength; ++i) {
+ if (!s[i] || (s[i] & 0xff) != u[i]) {
+ return gFalse;
+ }
+ }
+ return gTrue;
+}
+
+//------------------------------------------------------------------------
+// 'picture' formatting
+//------------------------------------------------------------------------
+
+class PictureNode {
+public:
+ virtual ~PictureNode() {}
+ virtual GBool isLiteral() { return gFalse; }
+ virtual GBool isSign() { return gFalse; }
+ virtual GBool isDigit() { return gFalse; }
+ virtual GBool isDecPt() { return gFalse; }
+ virtual GBool isSeparator() { return gFalse; }
+ virtual GBool isYear() { return gFalse; }
+ virtual GBool isMonth() { return gFalse; }
+ virtual GBool isDay() { return gFalse; }
+ virtual GBool isHour() { return gFalse; }
+ virtual GBool isMinute() { return gFalse; }
+ virtual GBool isSecond() { return gFalse; }
+ virtual GBool isChar() { return gFalse; }
+};
+
+class PictureLiteral: public PictureNode {
+public:
+ PictureLiteral(GString *sA) { s = sA; }
+ virtual ~PictureLiteral() { delete s; }
+ virtual GBool isLiteral() { return gTrue; }
+ GString *s;
+};
+
+class PictureSign: public PictureNode {
+public:
+ PictureSign(char cA) { c = cA; }
+ virtual GBool isSign() { return gTrue; }
+ char c;
+};
+
+class PictureDigit: public PictureNode {
+public:
+ PictureDigit(char cA) { c = cA; pos = 0; }
+ virtual GBool isDigit() { return gTrue; }
+ char c;
+ int pos;
+};
+
+class PictureDecPt: public PictureNode {
+public:
+ PictureDecPt() { }
+ virtual GBool isDecPt() { return gTrue; }
+};
+
+class PictureSeparator: public PictureNode {
+public:
+ PictureSeparator() { }
+ virtual GBool isSeparator() { return gTrue; }
+};
+
+class PictureYear: public PictureNode {
+public:
+ PictureYear(int nDigitsA) { nDigits = nDigitsA; }
+ virtual GBool isYear() { return gTrue; }
+ int nDigits;
+};
+
+class PictureMonth: public PictureNode {
+public:
+ PictureMonth(int nDigitsA) { nDigits = nDigitsA; }
+ virtual GBool isMonth() { return gTrue; }
+ int nDigits;
+};
+
+class PictureDay: public PictureNode {
+public:
+ PictureDay(int nDigitsA) { nDigits = nDigitsA; }
+ virtual GBool isDay() { return gTrue; }
+ int nDigits;
+};
+
+class PictureHour: public PictureNode {
+public:
+ PictureHour(GBool is24HourA, int nDigitsA)
+ { is24Hour = is24HourA; nDigits = nDigitsA; }
+ virtual GBool isHour() { return gTrue; }
+ GBool is24Hour;
+ int nDigits;
+};
+
+class PictureMinute: public PictureNode {
+public:
+ PictureMinute(int nDigitsA) { nDigits = nDigitsA; }
+ virtual GBool isMinute() { return gTrue; }
+ int nDigits;
+};
+
+class PictureSecond: public PictureNode {
+public:
+ PictureSecond(int nDigitsA) { nDigits = nDigitsA; }
+ virtual GBool isSecond() { return gTrue; }
+ int nDigits;
+};
+
+class PictureChar: public PictureNode {
+public:
+ PictureChar() {}
+ virtual GBool isChar() { return gTrue; }
+};
+
+GString *AcroFormField::pictureFormatDateTime(GString *value,
+ GString *picture) {
+ GList *pic;
+ PictureNode *node;
+ GString *ret, *s;
+ char c;
+ int year, month, day, hour, min, sec;
+ int len, picStart, picEnd, u, n, i, j;
+
+ len = value->getLength();
+ if (len == 0) {
+ return value->copy();
+ }
+
+ //--- parse the value
+
+ // expected format is yyyy(-mm(-dd)?)?Thh(:mm(:ss)?)?
+ // where:
+ // - the '-'s and ':'s are optional
+ // - the 'T' is literal
+ // - we're ignoring optional time zone info at the end
+ // (if the value is not in this canonical format, we just punt and
+ // return the value string)
+ //~ another option would be to parse the value following the
+ //~ <ui><picture> element
+ year = month = day = hour = min = sec = 0;
+ i = 0;
+ if (!(i + 4 <= len && isValidInt(value, i, 4))) {
+ return value->copy();
+ }
+ year = convertInt(value, i, 4);
+ i += 4;
+ if (i < len && value->getChar(i) == '-') {
+ ++i;
+ }
+ if (i + 2 <= len && isValidInt(value, i, 2)) {
+ month = convertInt(value, i, 2);
+ i += 2;
+ if (i < len && value->getChar(i) == '-') {
+ ++i;
+ }
+ if (i + 2 <= len && isValidInt(value, i, 2)) {
+ day = convertInt(value, i, 2);
+ i += 2;
+ }
+ }
+ if (i < len) {
+ if (value->getChar(i) != 'T') {
+ return value->copy();
+ }
+ ++i;
+ if (!(i + 2 <= len && isValidInt(value, i, 2))) {
+ return value->copy();
+ }
+ hour = convertInt(value, i, 2);
+ i += 2;
+ if (i < len && value->getChar(i) == ':') {
+ ++i;
+ }
+ if (i + 2 <= len && isValidInt(value, i, 2)) {
+ min = convertInt(value, i, 2);
+ i += 2;
+ if (i < len && value->getChar(i) == ':') {
+ ++i;
+ }
+ if (i + 2 <= len && isValidInt(value, i, 2)) {
+ sec = convertInt(value, i, 2);
+ i += 2;
+ }
+ }
+ }
+ if (i < len) {
+ return value->copy();
+ }
+
+ //--- skip the category and locale in the picture
+
+ picStart = 0;
+ picEnd = picture->getLength();
+ for (i = 0; i < picture->getLength(); ++i) {
+ c = picture->getChar(i);
+ if (c == '{') {
+ picStart = i + 1;
+ for (picEnd = picStart;
+ picEnd < picture->getLength() && picture->getChar(picEnd) != '}';
+ ++picEnd) ;
+ break;
+ } else if (!((c >= 'a' && c <= 'z') ||
+ (c >= 'A' && c <= 'Z') ||
+ c == '(' ||
+ c == ')')) {
+ break;
+ }
+ }
+
+ //--- parse the picture
+
+ pic = new GList();
+ i = picStart;
+ while (i < picEnd) {
+ c = picture->getChar(i);
+ ++i;
+ if (c == '\'') {
+ s = new GString();
+ while (i < picEnd) {
+ c = picture->getChar(i);
+ if (c == '\'') {
+ ++i;
+ if (i < picEnd && picture->getChar(i) == '\'') {
+ s->append('\'');
+ ++i;
+ } else {
+ break;
+ }
+ } else if (c == '\\') {
+ ++i;
+ if (i == picEnd) {
+ break;
+ }
+ c = picture->getChar(i);
+ ++i;
+ if (c == 'u' && i+4 <= picEnd) {
+ u = 0;
+ for (j = 0; j < 4; ++j, ++i) {
+ c = picture->getChar(i);
+ u <<= 4;
+ if (c >= '0' && c <= '9') {
+ u += c - '0';
+ } else if (c >= 'a' && c <= 'f') {
+ u += c - 'a' + 10;
+ } else if (c >= 'A' && c <= 'F') {
+ u += c - 'A' + 10;
+ }
+ }
+ //~ this should convert to UTF-8 (?)
+ if (u <= 0xff) {
+ s->append((char)u);
+ }
+ } else {
+ s->append(c);
+ }
+ } else {
+ s->append(c);
+ }
+ }
+ pic->append(new PictureLiteral(s));
+ } else if (c == ',' || c == '-' || c == ':' ||
+ c == '/' || c == '.' || c == ' ') {
+ s = new GString();
+ s->append(c);
+ pic->append(new PictureLiteral(s));
+ } else if (c == 'Y') {
+ for (n = 1; n < 4 && i < picEnd && picture->getChar(i) == 'Y'; ++n, ++i) ;
+ pic->append(new PictureYear(n));
+ } else if (c == 'M') {
+ for (n = 1; n < 2 && i < picEnd && picture->getChar(i) == 'M'; ++n, ++i) ;
+ pic->append(new PictureMonth(n));
+ } else if (c == 'D') {
+ for (n = 1; n < 2 && i < picEnd && picture->getChar(i) == 'D'; ++n, ++i) ;
+ pic->append(new PictureDay(n));
+ } else if (c == 'h') {
+ for (n = 1; n < 2 && i < picEnd && picture->getChar(i) == 'h'; ++n, ++i) ;
+ pic->append(new PictureHour(gFalse, n));
+ } else if (c == 'H') {
+ for (n = 1; n < 2 && i < picEnd && picture->getChar(i) == 'H'; ++n, ++i) ;
+ pic->append(new PictureHour(gTrue, n));
+ } else if (c == 'M') {
+ for (n = 1; n < 2 && i < picEnd && picture->getChar(i) == 'M'; ++n, ++i) ;
+ pic->append(new PictureMinute(n));
+ } else if (c == 'S') {
+ for (n = 1; n < 2 && i < picEnd && picture->getChar(i) == 'S'; ++n, ++i) ;
+ pic->append(new PictureSecond(n));
+ }
+ }
+
+ //--- generate formatted text
+
+ ret = new GString();
+ for (i = 0; i < pic->getLength(); ++i) {
+ node = (PictureNode *)pic->get(i);
+ if (node->isLiteral()) {
+ ret->append(((PictureLiteral *)node)->s);
+ } else if (node->isYear()) {
+ if (((PictureYear *)node)->nDigits == 2) {
+ if (year >= 1930 && year < 2030) {
+ ret->appendf("{0:02d}", year % 100);
+ } else {
+ ret->append("??");
+ }
+ } else {
+ ret->appendf("{0:04d}", year);
+ }
+ } else if (node->isMonth()) {
+ if (((PictureMonth *)node)->nDigits == 1) {
+ ret->appendf("{0:d}", month);
+ } else {
+ ret->appendf("{0:02d}", month);
+ }
+ } else if (node->isDay()) {
+ if (((PictureDay *)node)->nDigits == 1) {
+ ret->appendf("{0:d}", day);
+ } else {
+ ret->appendf("{0:02d}", day);
+ }
+ } else if (node->isHour()) {
+ if (((PictureHour *)node)->is24Hour) {
+ n = hour;
+ } else {
+ n = hour % 12;
+ if (n == 0) {
+ n = 12;
+ }
+ }
+ if (((PictureHour *)node)->nDigits == 1) {
+ ret->appendf("{0:d}", n);
+ } else {
+ ret->appendf("{0:02d}", n);
+ }
+ } else if (node->isMinute()) {
+ if (((PictureMinute *)node)->nDigits == 1) {
+ ret->appendf("{0:d}", min);
+ } else {
+ ret->appendf("{0:02d}", min);
+ }
+ } else if (node->isSecond()) {
+ if (((PictureSecond *)node)->nDigits == 1) {
+ ret->appendf("{0:d}", sec);
+ } else {
+ ret->appendf("{0:02d}", sec);
+ }
+ }
+ }
+ deleteGList(pic, PictureNode);
+
+ return ret;
+}
+
+GString *AcroFormField::pictureFormatNumber(GString *value, GString *picture) {
+ GList *pic;
+ PictureNode *node;
+ GString *ret, *s;
+ GBool neg, haveDigits;
+ char c;
+ int start, decPt, trailingZero, len;
+ int picStart, picEnd, u, pos, i, j;
+
+ len = value->getLength();
+ if (len == 0) {
+ return value->copy();
+ }
+
+ //--- parse the value
+
+ // -nnnn.nnnn0000
+ // ^ ^ ^ ^
+ // | | | +-- len
+ // | | +------ trailingZero
+ // | +----------- decPt
+ // +--------------- start
+ start = 0;
+ neg = gFalse;
+ if (value->getChar(start) == '-') {
+ neg = gTrue;
+ ++start;
+ } else if (value->getChar(start) == '+') {
+ ++start;
+ }
+ for (decPt = start; decPt < len && value->getChar(decPt) != '.'; ++decPt) ;
+ for (trailingZero = len;
+ trailingZero > decPt && value->getChar(trailingZero - 1) == '0';
+ --trailingZero) ;
+
+ //--- skip the category and locale in the picture
+
+ picStart = 0;
+ picEnd = picture->getLength();
+ for (i = 0; i < picture->getLength(); ++i) {
+ c = picture->getChar(i);
+ if (c == '{') {
+ picStart = i + 1;
+ for (picEnd = picStart;
+ picEnd < picture->getLength() && picture->getChar(picEnd) != '}';
+ ++picEnd) ;
+ break;
+ } else if (!((c >= 'a' && c <= 'z') ||
+ (c >= 'A' && c <= 'Z') ||
+ c == '(' ||
+ c == ')')) {
+ break;
+ }
+ }
+
+ //--- parse the picture
+
+ pic = new GList();
+ i = picStart;
+ while (i < picEnd) {
+ c = picture->getChar(i);
+ ++i;
+ if (c == '\'') {
+ s = new GString();
+ while (i < picEnd) {
+ c = picture->getChar(i);
+ if (c == '\'') {
+ ++i;
+ if (i < picEnd && picture->getChar(i) == '\'') {
+ s->append('\'');
+ ++i;
+ } else {
+ break;
+ }
+ } else if (c == '\\') {
+ ++i;
+ if (i == picEnd) {
+ break;
+ }
+ c = picture->getChar(i);
+ ++i;
+ if (c == 'u' && i+4 <= picEnd) {
+ u = 0;
+ for (j = 0; j < 4; ++j, ++i) {
+ c = picture->getChar(i);
+ u <<= 4;
+ if (c >= '0' && c <= '9') {
+ u += c - '0';
+ } else if (c >= 'a' && c <= 'F') {
+ u += c - 'a' + 10;
+ } else if (c >= 'A' && c <= 'F') {
+ u += c - 'A' + 10;
+ }
+ }
+ //~ this should convert to UTF-8 (?)
+ if (u <= 0xff) {
+ s->append((char)u);
+ }
+ } else {
+ s->append(c);
+ }
+ } else {
+ s->append(c);
+ ++i;
+ }
+ }
+ pic->append(new PictureLiteral(s));
+ } else if (c == '-' || c == ':' || c == '/' || c == ' ') {
+ s = new GString();
+ s->append(c);
+ pic->append(new PictureLiteral(s));
+ } else if (c == 's' || c == 'S') {
+ pic->append(new PictureSign(c));
+ } else if (c == 'Z' || c == 'z' || c == '8' || c == '9') {
+ pic->append(new PictureDigit(c));
+ } else if (c == '.') {
+ pic->append(new PictureDecPt());
+ } else if (c == ',') {
+ pic->append(new PictureSeparator());
+ }
+ }
+ for (i = 0; i < pic->getLength(); ++i) {
+ node = (PictureNode *)pic->get(i);
+ if (node->isDecPt()) {
+ break;
+ }
+ }
+ pos = 0;
+ for (j = i - 1; j >= 0; --j) {
+ node = (PictureNode *)pic->get(j);
+ if (node->isDigit()) {
+ ((PictureDigit *)node)->pos = pos;
+ ++pos;
+ }
+ }
+ pos = -1;
+ for (j = i + 1; j < pic->getLength(); ++j) {
+ node = (PictureNode *)pic->get(j);
+ if (node->isDigit()) {
+ ((PictureDigit *)node)->pos = pos;
+ --pos;
+ }
+ }
+
+ //--- generate formatted text
+
+ ret = new GString();
+ haveDigits = gFalse;
+ for (i = 0; i < pic->getLength(); ++i) {
+ node = (PictureNode *)pic->get(i);
+ if (node->isLiteral()) {
+ ret->append(((PictureLiteral *)node)->s);
+ } else if (node->isSign()) {
+ if (((PictureSign *)node)->c == 'S') {
+ ret->append(neg ? '-' : ' ');
+ } else {
+ if (neg) {
+ ret->append('-');
+ }
+ }
+ } else if (node->isDigit()) {
+ pos = ((PictureDigit *)node)->pos;
+ c = ((PictureDigit *)node)->c;
+ if (pos >= 0 && pos < decPt - start) {
+ ret->append(value->getChar(decPt - 1 - pos));
+ haveDigits = gTrue;
+ } else if (pos < 0 && -pos <= trailingZero - decPt - 1) {
+ ret->append(value->getChar(decPt - pos));
+ haveDigits = gTrue;
+ } else if (c == '8' &&
+ pos < 0 &&
+ -pos <= len - decPt - 1) {
+ ret->append('0');
+ haveDigits = gTrue;
+ } else if (c == '9') {
+ ret->append('0');
+ haveDigits = gTrue;
+ } else if (c == 'Z' && pos >= 0) {
+ ret->append(' ');
+ }
+ } else if (node->isDecPt()) {
+ if (!(i+1 < pic->getLength() &&
+ ((PictureNode *)pic->get(i+1))->isDigit() &&
+ ((PictureDigit *)pic->get(i+1))->c == 'z') ||
+ trailingZero > decPt + 1) {
+ ret->append('.');
+ }
+ } else if (node->isSeparator()) {
+ if (haveDigits) {
+ ret->append(',');
+ }
+ }
+ }
+ deleteGList(pic, PictureNode);
+
+ return ret;
+}
+
+GString *AcroFormField::pictureFormatText(GString *value, GString *picture) {
+ GList *pic;
+ PictureNode *node;
+ GString *ret, *s;
+ char c;
+ int len, picStart, picEnd, u, i, j;
+
+ len = value->getLength();
+ if (len == 0) {
+ return value->copy();
+ }
+
+ //--- skip the category and locale in the picture
+
+ picStart = 0;
+ picEnd = picture->getLength();
+ for (i = 0; i < picture->getLength(); ++i) {
+ c = picture->getChar(i);
+ if (c == '{') {
+ picStart = i + 1;
+ for (picEnd = picStart;
+ picEnd < picture->getLength() && picture->getChar(picEnd) != '}';
+ ++picEnd) ;
+ break;
+ } else if (!((c >= 'a' && c <= 'z') ||
+ (c >= 'A' && c <= 'Z') ||
+ c == '(' ||
+ c == ')')) {
+ break;
+ }
+ }
+
+ //--- parse the picture
+
+ pic = new GList();
+ i = picStart;
+ while (i < picEnd) {
+ c = picture->getChar(i);
+ ++i;
+ if (c == '\'') {
+ s = new GString();
+ while (i < picEnd) {
+ c = picture->getChar(i);
+ if (c == '\'') {
+ ++i;
+ if (i < picEnd && picture->getChar(i) == '\'') {
+ s->append('\'');
+ ++i;
+ } else {
+ break;
+ }
+ } else if (c == '\\') {
+ ++i;
+ if (i == picEnd) {
+ break;
+ }
+ c = picture->getChar(i);
+ ++i;
+ if (c == 'u' && i+4 <= picEnd) {
+ u = 0;
+ for (j = 0; j < 4; ++j, ++i) {
+ c = picture->getChar(i);
+ u <<= 4;
+ if (c >= '0' && c <= '9') {
+ u += c - '0';
+ } else if (c >= 'a' && c <= 'F') {
+ u += c - 'a' + 10;
+ } else if (c >= 'A' && c <= 'F') {
+ u += c - 'A' + 10;
+ }
+ }
+ //~ this should convert to UTF-8 (?)
+ if (u <= 0xff) {
+ s->append((char)u);
+ }
+ } else {
+ s->append(c);
+ }
+ } else {
+ s->append(c);
+ ++i;
+ }
+ }
+ pic->append(new PictureLiteral(s));
+ } else if (c == ',' || c == '-' || c == ':' ||
+ c == '/' || c == '.' || c == ' ') {
+ s = new GString();
+ s->append(c);
+ pic->append(new PictureLiteral(s));
+ } else if (c == 'A' || c == 'X' || c == 'O' || c == '0' || c == '9') {
+ pic->append(new PictureChar());
+ }
+ }
+
+ //--- generate formatted text
+
+ ret = new GString();
+ j = 0;
+ for (i = 0; i < pic->getLength(); ++i) {
+ node = (PictureNode *)pic->get(i);
+ if (node->isLiteral()) {
+ ret->append(((PictureLiteral *)node)->s);
+ } else if (node->isChar()) {
+ // if there are more chars in the picture than in the value,
+ // Adobe renders the value as-is, without picture formatting
+ if (j >= value->getLength()) {
+ delete ret;
+ ret = value->copy();
+ break;
+ }
+ ret->append(value->getChar(j));
+ ++j;
+ }
+ }
+ deleteGList(pic, PictureNode);
+
+ return ret;
+}
+
+GBool AcroFormField::isValidInt(GString *s, int start, int len) {
+ int i;
+
+ for (i = 0; i < len; ++i) {
+ if (!(start + i < s->getLength() &&
+ s->getChar(start + i) >= '0' &&
+ s->getChar(start + i) <= '9')) {
+ return gFalse;
+ }
+ }
+ return gTrue;
+}
+
+int AcroFormField::convertInt(GString *s, int start, int len) {
+ char c;
+ int x, i;
+
+ x = 0;
+ for (i = 0; i < len && start + i < s->getLength(); ++i) {
+ c = s->getChar(start + i);
+ if (c < '0' || c > '9') {
+ break;
+ }
+ x = x * 10 + (c - '0');
+ }
+ return x;
+}