3941 lines
104 KiB
C++
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;
|
|
}
|