Files
DocumentServer-v-9.2.0/core/PdfFile/lib/xpdf/AcroForm.cc
Yajbir Singh f1b860b25c
Some checks failed
check / markdownlint (push) Has been cancelled
check / spellchecker (push) Has been cancelled
updated
2025-12-11 19:03:17 +05:30

3941 lines
104 KiB
C++

//========================================================================
//
// 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 "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 (!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 = flagsA ? gTrue : gFalse;
} 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();
if (flagsA)
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
// BUG
// 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();
}
GList* AcroFormField::getColorSpace(int *nElements)
{
Object daObj;
GList *daToks, *arrRes;
int i;
arrRes = new GList();
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")) {
arrRes->append(new double(atof(((GString *)daToks->get(i - 1))->getCString())));
break;
// handle the rg operator
} else if (i >= 3 && !((GString *)daToks->get(i))->cmp("rg")) {
arrRes->append(new double(atof(((GString *)daToks->get(i - 3))->getCString())));
arrRes->append(new double(atof(((GString *)daToks->get(i - 2))->getCString())));
arrRes->append(new double(atof(((GString *)daToks->get(i - 1))->getCString())));
break;
} else if (i >= 4 && !((GString *)daToks->get(i))->cmp("k")) {
arrRes->append(new double(atof(((GString *)daToks->get(i - 4))->getCString())));
arrRes->append(new double(atof(((GString *)daToks->get(i - 3))->getCString())));
arrRes->append(new double(atof(((GString *)daToks->get(i - 2))->getCString())));
arrRes->append(new double(atof(((GString *)daToks->get(i - 1))->getCString())));
break;
}
}
deleteGList(daToks, GString);
}
daObj.free();
*nElements = arrRes->getLength();
return arrRes;
}
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,
const char* AP, const char* AS, GBool hide) {
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 (hide && ((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, AP, AS);
}
}
// 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,
const char* AP, const char* AS) {
Object apObj, asObj, appearance, obj1;
//----- get the appearance stream
if (!strcmp("MK", AP) && annot->lookup("MK", &apObj)->isDict()) {
apObj.dictLookupNF(AS, &appearance);
} else if (annot->lookup("AP", &apObj)->isDict()) {
apObj.dictLookup(AP, &obj1);
if (obj1.isDict()) {
if (AS) {
obj1.dictLookupNF(AS, &appearance);
} else 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(AP ? AP : "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()) {
if (obj->isDict()) {
if (obj->dictGetLength())
return obj;
} else
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;
}