1 //========================================================================
2 //
3 // AcroForm.cc
4 //
5 // Copyright 2012 Glyph & Cog, LLC
6 //
7 //========================================================================
8
9 #include <aconf.h>
10
11 #ifdef USE_GCC_PRAGMAS
12 #pragma implementation
13 #endif
14
15 #include <stdlib.h>
16 #include <math.h>
17 #include "gmem.h"
18 #include "GString.h"
19 #include "GList.h"
20 #include "Error.h"
21 #include "Object.h"
22 #include "PDFDoc.h"
23 #include "TextString.h"
24 #include "Gfx.h"
25 #include "GfxFont.h"
26 #include "OptionalContent.h"
27 #include "Annot.h"
28 #include "Lexer.h"
29 #include "AcroForm.h"
30
31 //------------------------------------------------------------------------
32
33 #define acroFormFlagReadOnly (1 << 0) // all
34 #define acroFormFlagRequired (1 << 1) // all
35 #define acroFormFlagNoExport (1 << 2) // all
36 #define acroFormFlagMultiline (1 << 12) // text
37 #define acroFormFlagPassword (1 << 13) // text
38 #define acroFormFlagNoToggleToOff (1 << 14) // button
39 #define acroFormFlagRadio (1 << 15) // button
40 #define acroFormFlagPushbutton (1 << 16) // button
41 #define acroFormFlagCombo (1 << 17) // choice
42 #define acroFormFlagEdit (1 << 18) // choice
43 #define acroFormFlagSort (1 << 19) // choice
44 #define acroFormFlagFileSelect (1 << 20) // text
45 #define acroFormFlagMultiSelect (1 << 21) // choice
46 #define acroFormFlagDoNotSpellCheck (1 << 22) // text, choice
47 #define acroFormFlagDoNotScroll (1 << 23) // text
48 #define acroFormFlagComb (1 << 24) // text
49 #define acroFormFlagRadiosInUnison (1 << 25) // button
50 #define acroFormFlagRichText (1 << 25) // text
51 #define acroFormFlagCommitOnSelChange (1 << 26) // choice
52
53 #define acroFormQuadLeft 0
54 #define acroFormQuadCenter 1
55 #define acroFormQuadRight 2
56
57 #define annotFlagHidden 0x0002
58 #define annotFlagPrint 0x0004
59 #define annotFlagNoView 0x0020
60
61 // distance of Bezier control point from center for circle approximation
62 // = (4 * (sqrt(2) - 1) / 3) * r
63 #define bezierCircle 0.55228475
64
65 //------------------------------------------------------------------------
66
67 // map an annotation ref to a page number
68 class AcroFormAnnotPage {
69 public:
AcroFormAnnotPage(int annotNumA,int annotGenA,int pageNumA)70 AcroFormAnnotPage(int annotNumA, int annotGenA, int pageNumA)
71 { annotNum = annotNumA; annotGen = annotGenA; pageNum = pageNumA; }
72 int annotNum;
73 int annotGen;
74 int pageNum;
75 };
76
77 //------------------------------------------------------------------------
78 // AcroForm
79 //------------------------------------------------------------------------
80
load(PDFDoc * docA,Catalog * catalog,Object * acroFormObjA)81 AcroForm *AcroForm::load(PDFDoc *docA, Catalog *catalog, Object *acroFormObjA) {
82 AcroForm *acroForm;
83 Object fieldsObj, obj1, obj2;
84 int i;
85
86 acroForm = new AcroForm(docA, acroFormObjA);
87
88 if (acroFormObjA->dictLookup("NeedAppearances", &obj1)->isBool()) {
89 acroForm->needAppearances = obj1.getBool();
90 }
91 obj1.free();
92
93 acroForm->buildAnnotPageList(catalog);
94
95 if (!acroFormObjA->dictLookup("Fields", &obj1)->isArray()) {
96 if (!obj1.isNull()) {
97 error(errSyntaxError, -1, "AcroForm Fields entry is wrong type");
98 }
99 obj1.free();
100 delete acroForm;
101 return NULL;
102 }
103 for (i = 0; i < obj1.arrayGetLength(); ++i) {
104 obj1.arrayGetNF(i, &obj2);
105 acroForm->scanField(&obj2);
106 obj2.free();
107 }
108 obj1.free();
109
110 return acroForm;
111 }
112
AcroForm(PDFDoc * docA,Object * acroFormObjA)113 AcroForm::AcroForm(PDFDoc *docA, Object *acroFormObjA): Form(docA) {
114 acroFormObjA->copy(&acroFormObj);
115 needAppearances = gFalse;
116 annotPages = new GList();
117 fields = new GList();
118 }
119
~AcroForm()120 AcroForm::~AcroForm() {
121 acroFormObj.free();
122 deleteGList(annotPages, AcroFormAnnotPage);
123 deleteGList(fields, AcroFormField);
124 }
125
buildAnnotPageList(Catalog * catalog)126 void AcroForm::buildAnnotPageList(Catalog *catalog) {
127 Object annotsObj, annotObj;
128 int pageNum, i;
129
130 for (pageNum = 1; pageNum <= catalog->getNumPages(); ++pageNum) {
131 if (catalog->getPage(pageNum)->getAnnots(&annotsObj)->isArray()) {
132 for (i = 0; i < annotsObj.arrayGetLength(); ++i) {
133 if (annotsObj.arrayGetNF(i, &annotObj)->isRef()) {
134 annotPages->append(new AcroFormAnnotPage(annotObj.getRefNum(),
135 annotObj.getRefGen(),
136 pageNum));
137 }
138 annotObj.free();
139 }
140 }
141 annotsObj.free();
142 }
143 //~ sort the list
144 }
145
lookupAnnotPage(Object * annotRef)146 int AcroForm::lookupAnnotPage(Object *annotRef) {
147 AcroFormAnnotPage *annotPage;
148 int num, gen, i;
149
150 if (!annotRef->isRef()) {
151 return 0;
152 }
153 num = annotRef->getRefNum();
154 gen = annotRef->getRefGen();
155 //~ use bin search
156 for (i = 0; i < annotPages->getLength(); ++i) {
157 annotPage = (AcroFormAnnotPage *)annotPages->get(i);
158 if (annotPage->annotNum == num && annotPage->annotGen == gen) {
159 return annotPage->pageNum;
160 }
161 }
162 return 0;
163 }
164
scanField(Object * fieldRef)165 void AcroForm::scanField(Object *fieldRef) {
166 AcroFormField *field;
167 Object fieldObj, kidsObj, kidRef, kidObj, subtypeObj;
168 GBool isTerminal;
169 int i;
170
171 fieldRef->fetch(doc->getXRef(), &fieldObj);
172 if (!fieldObj.isDict()) {
173 error(errSyntaxError, -1, "AcroForm field object is wrong type");
174 fieldObj.free();
175 return;
176 }
177
178 // if this field has a Kids array, and all of the kids have a Parent
179 // reference (i.e., they're all form fields, not widget
180 // annotations), then this is a non-terminal field, and we need to
181 // scan the kids
182 isTerminal = gTrue;
183 if (fieldObj.dictLookup("Kids", &kidsObj)->isArray()) {
184 isTerminal = gFalse;
185 for (i = 0; !isTerminal && i < kidsObj.arrayGetLength(); ++i) {
186 kidsObj.arrayGet(i, &kidObj);
187 if (kidObj.isDict()) {
188 if (kidObj.dictLookup("Parent", &subtypeObj)->isNull()) {
189 isTerminal = gTrue;
190 }
191 subtypeObj.free();
192 }
193 kidObj.free();
194 }
195 if (!isTerminal) {
196 for (i = 0; !isTerminal && i < kidsObj.arrayGetLength(); ++i) {
197 kidsObj.arrayGetNF(i, &kidRef);
198 scanField(&kidRef);
199 kidRef.free();
200 }
201 }
202 }
203 kidsObj.free();
204
205 if (isTerminal) {
206 if ((field = AcroFormField::load(this, fieldRef))) {
207 fields->append(field);
208 }
209 }
210
211 fieldObj.free();
212 }
213
draw(int pageNum,Gfx * gfx,GBool printing)214 void AcroForm::draw(int pageNum, Gfx *gfx, GBool printing) {
215 int i;
216
217 for (i = 0; i < fields->getLength(); ++i) {
218 ((AcroFormField *)fields->get(i))->draw(pageNum, gfx, printing);
219 }
220 }
221
getNumFields()222 int AcroForm::getNumFields() {
223 return fields->getLength();
224 }
225
getField(int idx)226 FormField *AcroForm::getField(int idx) {
227 return (AcroFormField *)fields->get(idx);
228 }
229
230 //------------------------------------------------------------------------
231 // AcroFormField
232 //------------------------------------------------------------------------
233
load(AcroForm * acroFormA,Object * fieldRefA)234 AcroFormField *AcroFormField::load(AcroForm *acroFormA, Object *fieldRefA) {
235 GString *typeStr;
236 TextString *nameA;
237 Guint flagsA;
238 GBool haveFlags;
239 Object fieldObjA, parentObj, parentObj2, obj1, obj2;
240 AcroFormFieldType typeA;
241 AcroFormField *field;
242
243 fieldRefA->fetch(acroFormA->doc->getXRef(), &fieldObjA);
244
245 //----- get field info
246
247 if (fieldObjA.dictLookup("T", &obj1)->isString()) {
248 nameA = new TextString(obj1.getString());
249 } else {
250 nameA = new TextString();
251 }
252 obj1.free();
253
254 if (fieldObjA.dictLookup("FT", &obj1)->isName()) {
255 typeStr = new GString(obj1.getName());
256 } else {
257 typeStr = NULL;
258 }
259 obj1.free();
260
261 if (fieldObjA.dictLookup("Ff", &obj1)->isInt()) {
262 flagsA = (Guint)obj1.getInt();
263 haveFlags = gTrue;
264 } else {
265 flagsA = 0;
266 haveFlags = gFalse;
267 }
268 obj1.free();
269
270 //----- get info from parent non-terminal fields
271
272 fieldObjA.dictLookup("Parent", &parentObj);
273 while (parentObj.isDict()) {
274
275 if (parentObj.dictLookup("T", &obj1)->isString()) {
276 if (nameA->getLength()) {
277 nameA->insert(0, (Unicode)'.');
278 }
279 nameA->insert(0, obj1.getString());
280 }
281 obj1.free();
282
283 if (!typeStr) {
284 if (parentObj.dictLookup("FT", &obj1)->isName()) {
285 typeStr = new GString(obj1.getName());
286 }
287 obj1.free();
288 }
289
290 if (!haveFlags) {
291 if (parentObj.dictLookup("Ff", &obj1)->isInt()) {
292 flagsA = (Guint)obj1.getInt();
293 haveFlags = gTrue;
294 }
295 obj1.free();
296 }
297
298 parentObj.dictLookup("Parent", &parentObj2);
299 parentObj.free();
300 parentObj = parentObj2;
301 }
302 parentObj.free();
303
304 if (!typeStr) {
305 error(errSyntaxError, -1, "Missing type in AcroForm field");
306 goto err1;
307 } else if (!typeStr->cmp("Btn")) {
308 if (flagsA & acroFormFlagPushbutton) {
309 typeA = acroFormFieldPushbutton;
310 } else if (flagsA & acroFormFlagRadio) {
311 typeA = acroFormFieldRadioButton;
312 } else {
313 typeA = acroFormFieldCheckbox;
314 }
315 } else if (!typeStr->cmp("Tx")) {
316 if (flagsA & acroFormFlagFileSelect) {
317 typeA = acroFormFieldFileSelect;
318 } else if (flagsA & acroFormFlagMultiline) {
319 typeA = acroFormFieldMultilineText;
320 } else {
321 typeA = acroFormFieldText;
322 }
323 } else if (!typeStr->cmp("Ch")) {
324 if (flagsA & acroFormFlagCombo) {
325 typeA = acroFormFieldComboBox;
326 } else {
327 typeA = acroFormFieldListBox;
328 }
329 } else if (!typeStr->cmp("Sig")) {
330 typeA = acroFormFieldSignature;
331 } else {
332 error(errSyntaxError, -1, "Invalid type in AcroForm field");
333 goto err1;
334 }
335 delete typeStr;
336
337 field = new AcroFormField(acroFormA, fieldRefA, &fieldObjA,
338 typeA, nameA, flagsA);
339 fieldObjA.free();
340 return field;
341
342 err1:
343 delete typeStr;
344 delete nameA;
345 fieldObjA.free();
346 return NULL;
347 }
348
AcroFormField(AcroForm * acroFormA,Object * fieldRefA,Object * fieldObjA,AcroFormFieldType typeA,TextString * nameA,Guint flagsA)349 AcroFormField::AcroFormField(AcroForm *acroFormA,
350 Object *fieldRefA, Object *fieldObjA,
351 AcroFormFieldType typeA, TextString *nameA,
352 Guint flagsA) {
353 acroForm = acroFormA;
354 fieldRefA->copy(&fieldRef);
355 fieldObjA->copy(&fieldObj);
356 type = typeA;
357 name = nameA;
358 flags = flagsA;
359 }
360
~AcroFormField()361 AcroFormField::~AcroFormField() {
362 fieldRef.free();
363 fieldObj.free();
364 delete name;
365 }
366
getType()367 const char *AcroFormField::getType() {
368 switch (type) {
369 case acroFormFieldPushbutton: return "PushButton";
370 case acroFormFieldRadioButton: return "RadioButton";
371 case acroFormFieldCheckbox: return "Checkbox";
372 case acroFormFieldFileSelect: return "FileSelect";
373 case acroFormFieldMultilineText: return "MultilineText";
374 case acroFormFieldText: return "Text";
375 case acroFormFieldComboBox: return "ComboBox";
376 case acroFormFieldListBox: return "ListBox";
377 case acroFormFieldSignature: return "Signature";
378 default: return NULL;
379 }
380 }
381
getName(int * length)382 Unicode *AcroFormField::getName(int *length) {
383 Unicode *u, *ret;
384 int n;
385
386 u = name->getUnicode();
387 n = name->getLength();
388 ret = (Unicode *)gmallocn(n, sizeof(Unicode));
389 memcpy(ret, u, n * sizeof(Unicode));
390 *length = n;
391 return ret;
392 }
393
getValue(int * length)394 Unicode *AcroFormField::getValue(int *length) {
395 Object obj1;
396 Unicode *u;
397 char *s;
398 TextString *ts;
399 int n, i;
400
401 fieldLookup("V", &obj1);
402 if (obj1.isName()) {
403 s = obj1.getName();
404 n = (int)strlen(s);
405 u = (Unicode *)gmallocn(n, sizeof(Unicode));
406 for (i = 0; i < n; ++i) {
407 u[i] = s[i] & 0xff;
408 }
409 *length = n;
410 return u;
411 } else if (obj1.isString()) {
412 ts = new TextString(obj1.getString());
413 n = ts->getLength();
414 u = (Unicode *)gmallocn(n, sizeof(Unicode));
415 memcpy(u, ts->getUnicode(), n * sizeof(Unicode));
416 *length = n;
417 delete ts;
418 return u;
419 } else {
420 return NULL;
421 }
422 }
423
draw(int pageNum,Gfx * gfx,GBool printing)424 void AcroFormField::draw(int pageNum, Gfx *gfx, GBool printing) {
425 Object kidsObj, annotRef, annotObj;
426 int i;
427
428 // find the annotation object(s)
429 if (fieldObj.dictLookup("Kids", &kidsObj)->isArray()) {
430 for (i = 0; i < kidsObj.arrayGetLength(); ++i) {
431 kidsObj.arrayGetNF(i, &annotRef);
432 annotRef.fetch(acroForm->doc->getXRef(), &annotObj);
433 drawAnnot(pageNum, gfx, printing, &annotRef, &annotObj);
434 annotObj.free();
435 annotRef.free();
436 }
437 } else {
438 drawAnnot(pageNum, gfx, printing, &fieldRef, &fieldObj);
439 }
440 kidsObj.free();
441 }
442
drawAnnot(int pageNum,Gfx * gfx,GBool printing,Object * annotRef,Object * annotObj)443 void AcroFormField::drawAnnot(int pageNum, Gfx *gfx, GBool printing,
444 Object *annotRef, Object *annotObj) {
445 Object obj1, obj2;
446 double xMin, yMin, xMax, yMax, t;
447 int annotFlags;
448 GBool oc;
449
450 if (!annotObj->isDict()) {
451 return;
452 }
453
454 //----- get the page number
455
456 // the "P" (page) field in annotations is optional, so we can't
457 // depend on it here
458 if (acroForm->lookupAnnotPage(annotRef) != pageNum) {
459 return;
460 }
461
462 //----- check annotation flags
463
464 if (annotObj->dictLookup("F", &obj1)->isInt()) {
465 annotFlags = obj1.getInt();
466 } else {
467 annotFlags = 0;
468 }
469 obj1.free();
470 if ((annotFlags & annotFlagHidden) ||
471 (printing && !(annotFlags & annotFlagPrint)) ||
472 (!printing && (annotFlags & annotFlagNoView))) {
473 return;
474 }
475
476 //----- check the optional content entry
477
478 annotObj->dictLookupNF("OC", &obj1);
479 if (acroForm->doc->getOptionalContent()->evalOCObject(&obj1, &oc) && !oc) {
480 obj1.free();
481 return;
482 }
483 obj1.free();
484
485 //----- get the bounding box
486
487 if (annotObj->dictLookup("Rect", &obj1)->isArray() &&
488 obj1.arrayGetLength() == 4) {
489 xMin = yMin = xMax = yMax = 0;
490 if (obj1.arrayGet(0, &obj2)->isNum()) {
491 xMin = obj2.getNum();
492 }
493 obj2.free();
494 if (obj1.arrayGet(1, &obj2)->isNum()) {
495 yMin = obj2.getNum();
496 }
497 obj2.free();
498 if (obj1.arrayGet(2, &obj2)->isNum()) {
499 xMax = obj2.getNum();
500 }
501 obj2.free();
502 if (obj1.arrayGet(3, &obj2)->isNum()) {
503 yMax = obj2.getNum();
504 }
505 obj2.free();
506 if (xMin > xMax) {
507 t = xMin; xMin = xMax; xMax = t;
508 }
509 if (yMin > yMax) {
510 t = yMin; yMin = yMax; yMax = t;
511 }
512 } else {
513 error(errSyntaxError, -1, "Bad bounding box for annotation");
514 obj1.free();
515 return;
516 }
517 obj1.free();
518
519 //----- draw it
520
521 if (acroForm->needAppearances) {
522 drawNewAppearance(gfx, annotObj->getDict(),
523 xMin, yMin, xMax, yMax);
524 } else {
525 drawExistingAppearance(gfx, annotObj->getDict(),
526 xMin, yMin, xMax, yMax);
527 }
528 }
529
530 // Draw the existing appearance stream for a single annotation
531 // attached to this field.
drawExistingAppearance(Gfx * gfx,Dict * annot,double xMin,double yMin,double xMax,double yMax)532 void AcroFormField::drawExistingAppearance(Gfx *gfx, Dict *annot,
533 double xMin, double yMin,
534 double xMax, double yMax) {
535 Object apObj, asObj, appearance, obj1;
536
537 //----- get the appearance stream
538
539 if (annot->lookup("AP", &apObj)->isDict()) {
540 apObj.dictLookup("N", &obj1);
541 if (obj1.isDict()) {
542 if (annot->lookup("AS", &asObj)->isName()) {
543 obj1.dictLookupNF(asObj.getName(), &appearance);
544 } else if (obj1.dictGetLength() == 1) {
545 obj1.dictGetValNF(0, &appearance);
546 } else {
547 obj1.dictLookupNF("Off", &appearance);
548 }
549 asObj.free();
550 } else {
551 apObj.dictLookupNF("N", &appearance);
552 }
553 obj1.free();
554 }
555 apObj.free();
556
557 //----- draw it
558
559 if (!appearance.isNone()) {
560 gfx->drawAnnot(&appearance, NULL, xMin, yMin, xMax, yMax);
561 appearance.free();
562 }
563 }
564
565 // Regenerate the appearnce for this field, and draw it.
drawNewAppearance(Gfx * gfx,Dict * annot,double xMin,double yMin,double xMax,double yMax)566 void AcroFormField::drawNewAppearance(Gfx *gfx, Dict *annot,
567 double xMin, double yMin,
568 double xMax, double yMax) {
569 Object appearance, mkObj, ftObj, appearDict, drObj, apObj, asObj;
570 Object obj1, obj2, obj3;
571 Dict *mkDict;
572 MemStream *appearStream;
573 GfxFontDict *fontDict;
574 GBool hasCaption;
575 double dx, dy, r;
576 GString *caption, *da;
577 GString **text;
578 GBool *selection;
579 AnnotBorderType borderType;
580 double borderWidth;
581 double *borderDash;
582 GString *appearanceState;
583 int borderDashLength, rot, quadding, comb, nOptions, topIdx, i, j;
584
585 appearBuf = new GString();
586
587 // get the appearance characteristics (MK) dictionary
588 if (annot->lookup("MK", &mkObj)->isDict()) {
589 mkDict = mkObj.getDict();
590 } else {
591 mkDict = NULL;
592 }
593
594 // draw the background
595 if (mkDict) {
596 if (mkDict->lookup("BG", &obj1)->isArray() &&
597 obj1.arrayGetLength() > 0) {
598 setColor(obj1.getArray(), gTrue, 0);
599 appearBuf->appendf("0 0 {0:.4f} {1:.4f} re f\n",
600 xMax - xMin, yMax - yMin);
601 }
602 obj1.free();
603 }
604
605 // get the field type
606 fieldLookup("FT", &ftObj);
607
608 // draw the border
609 borderType = annotBorderSolid;
610 borderWidth = 1;
611 borderDash = NULL;
612 borderDashLength = 0;
613 if (annot->lookup("BS", &obj1)->isDict()) {
614 if (obj1.dictLookup("S", &obj2)->isName()) {
615 if (obj2.isName("S")) {
616 borderType = annotBorderSolid;
617 } else if (obj2.isName("D")) {
618 borderType = annotBorderDashed;
619 } else if (obj2.isName("B")) {
620 borderType = annotBorderBeveled;
621 } else if (obj2.isName("I")) {
622 borderType = annotBorderInset;
623 } else if (obj2.isName("U")) {
624 borderType = annotBorderUnderlined;
625 }
626 }
627 obj2.free();
628 if (obj1.dictLookup("W", &obj2)->isNum()) {
629 borderWidth = obj2.getNum();
630 }
631 obj2.free();
632 if (obj1.dictLookup("D", &obj2)->isArray()) {
633 borderDashLength = obj2.arrayGetLength();
634 borderDash = (double *)gmallocn(borderDashLength, sizeof(double));
635 for (i = 0; i < borderDashLength; ++i) {
636 if (obj2.arrayGet(i, &obj3)->isNum()) {
637 borderDash[i] = obj3.getNum();
638 } else {
639 borderDash[i] = 1;
640 }
641 obj3.free();
642 }
643 }
644 obj2.free();
645 } else {
646 obj1.free();
647 if (annot->lookup("Border", &obj1)->isArray()) {
648 if (obj1.arrayGetLength() >= 3) {
649 if (obj1.arrayGet(2, &obj2)->isNum()) {
650 borderWidth = obj2.getNum();
651 }
652 obj2.free();
653 if (obj1.arrayGetLength() >= 4) {
654 if (obj1.arrayGet(3, &obj2)->isArray()) {
655 borderType = annotBorderDashed;
656 borderDashLength = obj2.arrayGetLength();
657 borderDash = (double *)gmallocn(borderDashLength, sizeof(double));
658 for (i = 0; i < borderDashLength; ++i) {
659 if (obj2.arrayGet(i, &obj3)->isNum()) {
660 borderDash[i] = obj3.getNum();
661 } else {
662 borderDash[i] = 1;
663 }
664 obj3.free();
665 }
666 } else {
667 // Adobe draws no border at all if the last element is of
668 // the wrong type.
669 borderWidth = 0;
670 }
671 obj2.free();
672 }
673 }
674 }
675 }
676 obj1.free();
677 if (mkDict) {
678 if (borderWidth > 0) {
679 mkDict->lookup("BC", &obj1);
680 if (!(obj1.isArray() && obj1.arrayGetLength() > 0)) {
681 mkDict->lookup("BG", &obj1);
682 }
683 if (obj1.isArray() && obj1.arrayGetLength() > 0) {
684 dx = xMax - xMin;
685 dy = yMax - yMin;
686
687 // radio buttons with no caption have a round border
688 hasCaption = mkDict->lookup("CA", &obj2)->isString();
689 obj2.free();
690 if (ftObj.isName("Btn") && (flags & acroFormFlagRadio) && !hasCaption) {
691 r = 0.5 * (dx < dy ? dx : dy);
692 switch (borderType) {
693 case annotBorderDashed:
694 appearBuf->append("[");
695 for (i = 0; i < borderDashLength; ++i) {
696 appearBuf->appendf(" {0:.4f}", borderDash[i]);
697 }
698 appearBuf->append("] 0 d\n");
699 // fall through to the solid case
700 case annotBorderSolid:
701 case annotBorderUnderlined:
702 appearBuf->appendf("{0:.4f} w\n", borderWidth);
703 setColor(obj1.getArray(), gFalse, 0);
704 drawCircle(0.5 * dx, 0.5 * dy, r - 0.5 * borderWidth, "s");
705 break;
706 case annotBorderBeveled:
707 case annotBorderInset:
708 appearBuf->appendf("{0:.4f} w\n", 0.5 * borderWidth);
709 setColor(obj1.getArray(), gFalse, 0);
710 drawCircle(0.5 * dx, 0.5 * dy, r - 0.25 * borderWidth, "s");
711 setColor(obj1.getArray(), gFalse,
712 borderType == annotBorderBeveled ? 1 : -1);
713 drawCircleTopLeft(0.5 * dx, 0.5 * dy, r - 0.75 * borderWidth);
714 setColor(obj1.getArray(), gFalse,
715 borderType == annotBorderBeveled ? -1 : 1);
716 drawCircleBottomRight(0.5 * dx, 0.5 * dy, r - 0.75 * borderWidth);
717 break;
718 }
719
720 } else {
721 switch (borderType) {
722 case annotBorderDashed:
723 appearBuf->append("[");
724 for (i = 0; i < borderDashLength; ++i) {
725 appearBuf->appendf(" {0:.4f}", borderDash[i]);
726 }
727 appearBuf->append("] 0 d\n");
728 // fall through to the solid case
729 case annotBorderSolid:
730 appearBuf->appendf("{0:.4f} w\n", borderWidth);
731 setColor(obj1.getArray(), gFalse, 0);
732 appearBuf->appendf("{0:.4f} {0:.4f} {1:.4f} {2:.4f} re s\n",
733 0.5 * borderWidth,
734 dx - borderWidth, dy - borderWidth);
735 break;
736 case annotBorderBeveled:
737 case annotBorderInset:
738 setColor(obj1.getArray(), gTrue,
739 borderType == annotBorderBeveled ? 1 : -1);
740 appearBuf->append("0 0 m\n");
741 appearBuf->appendf("0 {0:.4f} l\n", dy);
742 appearBuf->appendf("{0:.4f} {1:.4f} l\n", dx, dy);
743 appearBuf->appendf("{0:.4f} {1:.4f} l\n",
744 dx - borderWidth, dy - borderWidth);
745 appearBuf->appendf("{0:.4f} {1:.4f} l\n",
746 borderWidth, dy - borderWidth);
747 appearBuf->appendf("{0:.4f} {0:.4f} l\n", borderWidth);
748 appearBuf->append("f\n");
749 setColor(obj1.getArray(), gTrue,
750 borderType == annotBorderBeveled ? -1 : 1);
751 appearBuf->append("0 0 m\n");
752 appearBuf->appendf("{0:.4f} 0 l\n", dx);
753 appearBuf->appendf("{0:.4f} {1:.4f} l\n", dx, dy);
754 appearBuf->appendf("{0:.4f} {1:.4f} l\n",
755 dx - borderWidth, dy - borderWidth);
756 appearBuf->appendf("{0:.4f} {1:.4f} l\n",
757 dx - borderWidth, borderWidth);
758 appearBuf->appendf("{0:.4f} {0:.4f} l\n", borderWidth);
759 appearBuf->append("f\n");
760 break;
761 case annotBorderUnderlined:
762 appearBuf->appendf("{0:.4f} w\n", borderWidth);
763 setColor(obj1.getArray(), gFalse, 0);
764 appearBuf->appendf("0 0 m {0:.4f} 0 l s\n", dx);
765 break;
766 }
767
768 // clip to the inside of the border
769 appearBuf->appendf("{0:.4f} {0:.4f} {1:.4f} {2:.4f} re W n\n",
770 borderWidth,
771 dx - 2 * borderWidth, dy - 2 * borderWidth);
772 }
773 }
774 obj1.free();
775 }
776 }
777 gfree(borderDash);
778
779 // get the resource dictionary
780 fieldLookup("DR", &drObj);
781
782 // build the font dictionary
783 if (drObj.isDict() && drObj.dictLookup("Font", &obj1)->isDict()) {
784 fontDict = new GfxFontDict(acroForm->doc->getXRef(), NULL, obj1.getDict());
785 } else {
786 fontDict = NULL;
787 }
788 obj1.free();
789
790 // get the default appearance string
791 if (fieldLookup("DA", &obj1)->isString()) {
792 da = obj1.getString()->copy();
793 } else {
794 da = NULL;
795 }
796 obj1.free();
797
798 // get the rotation value
799 rot = 0;
800 if (mkDict) {
801 if (mkDict->lookup("R", &obj1)->isInt()) {
802 rot = obj1.getInt();
803 }
804 obj1.free();
805 }
806
807 // get the appearance state
808 annot->lookup("AP", &apObj);
809 annot->lookup("AS", &asObj);
810 appearanceState = NULL;
811 if (asObj.isName()) {
812 appearanceState = new GString(asObj.getName());
813 } else if (apObj.isDict()) {
814 apObj.dictLookup("N", &obj1);
815 if (obj1.isDict() && obj1.dictGetLength() == 1) {
816 appearanceState = new GString(obj1.dictGetKey(0));
817 }
818 obj1.free();
819 }
820 if (!appearanceState) {
821 appearanceState = new GString("Off");
822 }
823 asObj.free();
824 apObj.free();
825
826 // draw the field contents
827 if (ftObj.isName("Btn")) {
828 caption = NULL;
829 if (mkDict) {
830 if (mkDict->lookup("CA", &obj1)->isString()) {
831 caption = obj1.getString()->copy();
832 }
833 obj1.free();
834 }
835 // radio button
836 if (flags & acroFormFlagRadio) {
837 //~ Acrobat doesn't draw a caption if there is no AP dict (?)
838 if (fieldLookup("V", &obj1)
839 ->isName(appearanceState->getCString())) {
840 if (caption) {
841 drawText(caption, da, fontDict, gFalse, 0, acroFormQuadCenter,
842 gFalse, gTrue, rot, xMin, yMin, xMax, yMax, borderWidth);
843 } else {
844 if (mkDict) {
845 if (mkDict->lookup("BC", &obj2)->isArray() &&
846 obj2.arrayGetLength() > 0) {
847 dx = xMax - xMin;
848 dy = yMax - yMin;
849 setColor(obj2.getArray(), gTrue, 0);
850 drawCircle(0.5 * dx, 0.5 * dy, 0.2 * (dx < dy ? dx : dy), "f");
851 }
852 obj2.free();
853 }
854 }
855 }
856 obj1.free();
857 // pushbutton
858 } else if (flags & acroFormFlagPushbutton) {
859 if (caption) {
860 drawText(caption, da, fontDict, gFalse, 0, acroFormQuadCenter,
861 gFalse, gFalse, rot, xMin, yMin, xMax, yMax, borderWidth);
862 }
863 // checkbox
864 } else {
865 fieldLookup("V", &obj1);
866 if (obj1.isName() && !obj1.isName("Off")) {
867 if (!caption) {
868 caption = new GString("3"); // ZapfDingbats checkmark
869 }
870 drawText(caption, da, fontDict, gFalse, 0, acroFormQuadCenter,
871 gFalse, gTrue, rot, xMin, yMin, xMax, yMax, borderWidth);
872 }
873 obj1.free();
874 }
875 if (caption) {
876 delete caption;
877 }
878 } else if (ftObj.isName("Tx")) {
879 //~ value strings can be Unicode
880 if (!fieldLookup("V", &obj1)->isString()) {
881 obj1.free();
882 fieldLookup("DV", &obj1);
883 }
884 if (obj1.isString()) {
885 if (fieldLookup("Q", &obj2)->isInt()) {
886 quadding = obj2.getInt();
887 } else {
888 quadding = acroFormQuadLeft;
889 }
890 obj2.free();
891 comb = 0;
892 if (flags & acroFormFlagComb) {
893 if (fieldLookup("MaxLen", &obj2)->isInt()) {
894 comb = obj2.getInt();
895 }
896 obj2.free();
897 }
898 drawText(obj1.getString(), da, fontDict,
899 flags & acroFormFlagMultiline, comb, quadding,
900 gTrue, gFalse, rot, xMin, yMin, xMax, yMax, borderWidth);
901 }
902 obj1.free();
903 } else if (ftObj.isName("Ch")) {
904 //~ value/option strings can be Unicode
905 if (fieldLookup("Q", &obj1)->isInt()) {
906 quadding = obj1.getInt();
907 } else {
908 quadding = acroFormQuadLeft;
909 }
910 obj1.free();
911 // combo box
912 if (flags & acroFormFlagCombo) {
913 if (fieldLookup("V", &obj1)->isString()) {
914 drawText(obj1.getString(), da, fontDict,
915 gFalse, 0, quadding, gTrue, gFalse, rot,
916 xMin, yMin, xMax, yMax, borderWidth);
917 //~ Acrobat draws a popup icon on the right side
918 }
919 obj1.free();
920 // list box
921 } else {
922 if (fieldObj.dictLookup("Opt", &obj1)->isArray()) {
923 nOptions = obj1.arrayGetLength();
924 // get the option text
925 text = (GString **)gmallocn(nOptions, sizeof(GString *));
926 for (i = 0; i < nOptions; ++i) {
927 text[i] = NULL;
928 obj1.arrayGet(i, &obj2);
929 if (obj2.isString()) {
930 text[i] = obj2.getString()->copy();
931 } else if (obj2.isArray() && obj2.arrayGetLength() == 2) {
932 if (obj2.arrayGet(1, &obj3)->isString()) {
933 text[i] = obj3.getString()->copy();
934 }
935 obj3.free();
936 }
937 obj2.free();
938 if (!text[i]) {
939 text[i] = new GString();
940 }
941 }
942 // get the selected option(s)
943 selection = (GBool *)gmallocn(nOptions, sizeof(GBool));
944 //~ need to use the I field in addition to the V field
945 fieldLookup("V", &obj2);
946 for (i = 0; i < nOptions; ++i) {
947 selection[i] = gFalse;
948 if (obj2.isString()) {
949 if (!obj2.getString()->cmp(text[i])) {
950 selection[i] = gTrue;
951 }
952 } else if (obj2.isArray()) {
953 for (j = 0; j < obj2.arrayGetLength(); ++j) {
954 if (obj2.arrayGet(j, &obj3)->isString() &&
955 !obj3.getString()->cmp(text[i])) {
956 selection[i] = gTrue;
957 }
958 obj3.free();
959 }
960 }
961 }
962 obj2.free();
963 // get the top index
964 if (fieldObj.dictLookup("TI", &obj2)->isInt()) {
965 topIdx = obj2.getInt();
966 } else {
967 topIdx = 0;
968 }
969 obj2.free();
970 // draw the text
971 drawListBox(text, selection, nOptions, topIdx, da, fontDict, quadding,
972 xMin, yMin, xMax, yMax, borderWidth);
973 for (i = 0; i < nOptions; ++i) {
974 delete text[i];
975 }
976 gfree(text);
977 gfree(selection);
978 }
979 obj1.free();
980 }
981 } else if (ftObj.isName("Sig")) {
982 //~unimp
983 } else {
984 error(errSyntaxError, -1, "Unknown field type");
985 }
986
987 delete appearanceState;
988 if (da) {
989 delete da;
990 }
991
992 // build the appearance stream dictionary
993 appearDict.initDict(acroForm->doc->getXRef());
994 appearDict.dictAdd(copyString("Length"),
995 obj1.initInt(appearBuf->getLength()));
996 appearDict.dictAdd(copyString("Subtype"), obj1.initName("Form"));
997 obj1.initArray(acroForm->doc->getXRef());
998 obj1.arrayAdd(obj2.initReal(0));
999 obj1.arrayAdd(obj2.initReal(0));
1000 obj1.arrayAdd(obj2.initReal(xMax - xMin));
1001 obj1.arrayAdd(obj2.initReal(yMax - yMin));
1002 appearDict.dictAdd(copyString("BBox"), &obj1);
1003
1004 // set the resource dictionary
1005 if (drObj.isDict()) {
1006 appearDict.dictAdd(copyString("Resources"), drObj.copy(&obj1));
1007 }
1008 drObj.free();
1009
1010 // build the appearance stream
1011 appearStream = new MemStream(appearBuf->getCString(), 0,
1012 appearBuf->getLength(), &appearDict);
1013 appearance.initStream(appearStream);
1014
1015 // draw it
1016 gfx->drawAnnot(&appearance, NULL, xMin, yMin, xMax, yMax);
1017
1018 appearance.free();
1019 delete appearBuf;
1020 appearBuf = NULL;
1021 if (fontDict) {
1022 delete fontDict;
1023 }
1024 ftObj.free();
1025 mkObj.free();
1026 }
1027
1028 // Set the current fill or stroke color, based on <a> (which should
1029 // have 1, 3, or 4 elements). If <adjust> is +1, color is brightened;
1030 // if <adjust> is -1, color is darkened; otherwise color is not
1031 // modified.
setColor(Array * a,GBool fill,int adjust)1032 void AcroFormField::setColor(Array *a, GBool fill, int adjust) {
1033 Object obj1;
1034 double color[4];
1035 int nComps, i;
1036
1037 nComps = a->getLength();
1038 if (nComps > 4) {
1039 nComps = 4;
1040 }
1041 for (i = 0; i < nComps && i < 4; ++i) {
1042 if (a->get(i, &obj1)->isNum()) {
1043 color[i] = obj1.getNum();
1044 } else {
1045 color[i] = 0;
1046 }
1047 obj1.free();
1048 }
1049 if (nComps == 4) {
1050 adjust = -adjust;
1051 }
1052 if (adjust > 0) {
1053 for (i = 0; i < nComps; ++i) {
1054 color[i] = 0.5 * color[i] + 0.5;
1055 }
1056 } else if (adjust < 0) {
1057 for (i = 0; i < nComps; ++i) {
1058 color[i] = 0.5 * color[i];
1059 }
1060 }
1061 if (nComps == 4) {
1062 appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:c}\n",
1063 color[0], color[1], color[2], color[3],
1064 fill ? 'k' : 'K');
1065 } else if (nComps == 3) {
1066 appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:s}\n",
1067 color[0], color[1], color[2],
1068 fill ? "rg" : "RG");
1069 } else {
1070 appearBuf->appendf("{0:.2f} {1:c}\n",
1071 color[0],
1072 fill ? 'g' : 'G');
1073 }
1074 }
1075
1076 // Draw the variable text or caption for a field.
drawText(GString * text,GString * da,GfxFontDict * fontDict,GBool multiline,int comb,int quadding,GBool txField,GBool forceZapfDingbats,int rot,double xMin,double yMin,double xMax,double yMax,double border)1077 void AcroFormField::drawText(GString *text, GString *da, GfxFontDict *fontDict,
1078 GBool multiline, int comb, int quadding,
1079 GBool txField, GBool forceZapfDingbats, int rot,
1080 double xMin, double yMin, double xMax, double yMax,
1081 double border) {
1082 GString *text2;
1083 GList *daToks;
1084 GString *tok;
1085 GfxFont *font;
1086 double dx, dy;
1087 double fontSize, fontSize2, x, xPrev, y, w, w2, wMax;
1088 int tfPos, tmPos, i, j, k, c;
1089
1090 //~ if there is no MK entry, this should use the existing content stream,
1091 //~ and only replace the marked content portion of it
1092 //~ (this is only relevant for Tx fields)
1093
1094 // check for a Unicode string
1095 //~ this currently drops all non-Latin1 characters
1096 if (text->getLength() >= 2 &&
1097 text->getChar(0) == '\xfe' && text->getChar(1) == '\xff') {
1098 text2 = new GString();
1099 for (i = 2; i+1 < text->getLength(); i += 2) {
1100 c = ((text->getChar(i) & 0xff) << 8) + (text->getChar(i+1) & 0xff);
1101 if (c <= 0xff) {
1102 text2->append((char)c);
1103 } else {
1104 text2->append('?');
1105 }
1106 }
1107 } else {
1108 text2 = text;
1109 }
1110
1111 // parse the default appearance string
1112 tfPos = tmPos = -1;
1113 if (da) {
1114 daToks = new GList();
1115 i = 0;
1116 while (i < da->getLength()) {
1117 while (i < da->getLength() && Lexer::isSpace(da->getChar(i))) {
1118 ++i;
1119 }
1120 if (i < da->getLength()) {
1121 for (j = i + 1;
1122 j < da->getLength() && !Lexer::isSpace(da->getChar(j));
1123 ++j) ;
1124 daToks->append(new GString(da, i, j - i));
1125 i = j;
1126 }
1127 }
1128 for (i = 2; i < daToks->getLength(); ++i) {
1129 if (i >= 2 && !((GString *)daToks->get(i))->cmp("Tf")) {
1130 tfPos = i - 2;
1131 } else if (i >= 6 && !((GString *)daToks->get(i))->cmp("Tm")) {
1132 tmPos = i - 6;
1133 }
1134 }
1135 } else {
1136 daToks = NULL;
1137 }
1138
1139 // force ZapfDingbats
1140 //~ this should create the font if needed (?)
1141 if (forceZapfDingbats) {
1142 if (tfPos >= 0) {
1143 tok = (GString *)daToks->get(tfPos);
1144 if (tok->cmp("/ZaDb")) {
1145 tok->clear();
1146 tok->append("/ZaDb");
1147 }
1148 }
1149 }
1150
1151 // get the font and font size
1152 font = NULL;
1153 fontSize = 0;
1154 if (tfPos >= 0) {
1155 tok = (GString *)daToks->get(tfPos);
1156 if (tok->getLength() >= 1 && tok->getChar(0) == '/') {
1157 if (!fontDict || !(font = fontDict->lookup(tok->getCString() + 1))) {
1158 error(errSyntaxError, -1, "Unknown font in field's DA string");
1159 }
1160 } else {
1161 error(errSyntaxError, -1,
1162 "Invalid font name in 'Tf' operator in field's DA string");
1163 }
1164 tok = (GString *)daToks->get(tfPos + 1);
1165 fontSize = atof(tok->getCString());
1166 } else {
1167 error(errSyntaxError, -1, "Missing 'Tf' operator in field's DA string");
1168 }
1169
1170 // setup
1171 if (txField) {
1172 appearBuf->append("/Tx BMC\n");
1173 }
1174 appearBuf->append("q\n");
1175 if (rot == 90) {
1176 appearBuf->appendf("0 1 -1 0 {0:.4f} 0 cm\n", xMax - xMin);
1177 dx = yMax - yMin;
1178 dy = xMax - xMin;
1179 } else if (rot == 180) {
1180 appearBuf->appendf("-1 0 0 -1 {0:.4f} {1:.4f} cm\n",
1181 xMax - xMin, yMax - yMin);
1182 dx = xMax - yMax;
1183 dy = yMax - yMin;
1184 } else if (rot == 270) {
1185 appearBuf->appendf("0 -1 1 0 0 {0:.4f} cm\n", yMax - yMin);
1186 dx = yMax - yMin;
1187 dy = xMax - xMin;
1188 } else { // assume rot == 0
1189 dx = xMax - xMin;
1190 dy = yMax - yMin;
1191 }
1192 appearBuf->append("BT\n");
1193
1194 // multi-line text
1195 if (multiline) {
1196 // note: the comb flag is ignored in multiline mode
1197
1198 wMax = dx - 2 * border - 4;
1199
1200 // compute font autosize
1201 if (fontSize == 0) {
1202 for (fontSize = 20; fontSize > 1; --fontSize) {
1203 y = dy - 3;
1204 w2 = 0;
1205 i = 0;
1206 while (i < text2->getLength()) {
1207 getNextLine(text2, i, font, fontSize, wMax, &j, &w, &k);
1208 if (w > w2) {
1209 w2 = w;
1210 }
1211 i = k;
1212 y -= fontSize;
1213 }
1214 // approximate the descender for the last line
1215 if (y >= 0.33 * fontSize) {
1216 break;
1217 }
1218 }
1219 if (tfPos >= 0) {
1220 tok = (GString *)daToks->get(tfPos + 1);
1221 tok->clear();
1222 tok->appendf("{0:.2f}", fontSize);
1223 }
1224 }
1225
1226 // starting y coordinate
1227 // (note: each line of text starts with a Td operator that moves
1228 // down a line)
1229 y = dy - 3;
1230
1231 // set the font matrix
1232 if (tmPos >= 0) {
1233 tok = (GString *)daToks->get(tmPos + 4);
1234 tok->clear();
1235 tok->append('0');
1236 tok = (GString *)daToks->get(tmPos + 5);
1237 tok->clear();
1238 tok->appendf("{0:.4f}", y);
1239 }
1240
1241 // write the DA string
1242 if (daToks) {
1243 for (i = 0; i < daToks->getLength(); ++i) {
1244 appearBuf->append((GString *)daToks->get(i))->append(' ');
1245 }
1246 }
1247
1248 // write the font matrix (if not part of the DA string)
1249 if (tmPos < 0) {
1250 appearBuf->appendf("1 0 0 1 0 {0:.4f} Tm\n", y);
1251 }
1252
1253 // write a series of lines of text
1254 i = 0;
1255 xPrev = 0;
1256 while (i < text2->getLength()) {
1257
1258 getNextLine(text2, i, font, fontSize, wMax, &j, &w, &k);
1259
1260 // compute text start position
1261 switch (quadding) {
1262 case acroFormQuadLeft:
1263 default:
1264 x = border + 2;
1265 break;
1266 case acroFormQuadCenter:
1267 x = (dx - w) / 2;
1268 break;
1269 case acroFormQuadRight:
1270 x = dx - border - 2 - w;
1271 break;
1272 }
1273
1274 // draw the line
1275 appearBuf->appendf("{0:.4f} {1:.4f} Td\n", x - xPrev, -fontSize);
1276 appearBuf->append('(');
1277 for (; i < j; ++i) {
1278 c = text2->getChar(i) & 0xff;
1279 if (c == '(' || c == ')' || c == '\\') {
1280 appearBuf->append('\\');
1281 appearBuf->append(c);
1282 } else if (c < 0x20 || c >= 0x80) {
1283 appearBuf->appendf("\\{0:03o}", c);
1284 } else {
1285 appearBuf->append(c);
1286 }
1287 }
1288 appearBuf->append(") Tj\n");
1289
1290 // next line
1291 i = k;
1292 xPrev = x;
1293 }
1294
1295 // single-line text
1296 } else {
1297 //~ replace newlines with spaces? - what does Acrobat do?
1298
1299 // comb formatting
1300 if (comb > 0) {
1301
1302 // compute comb spacing
1303 w = (dx - 2 * border) / comb;
1304
1305 // compute font autosize
1306 if (fontSize == 0) {
1307 fontSize = dy - 2 * border;
1308 if (w < fontSize) {
1309 fontSize = w;
1310 }
1311 fontSize = floor(fontSize);
1312 if (tfPos >= 0) {
1313 tok = (GString *)daToks->get(tfPos + 1);
1314 tok->clear();
1315 tok->appendf("{0:.4f}", fontSize);
1316 }
1317 }
1318
1319 // compute text start position
1320 switch (quadding) {
1321 case acroFormQuadLeft:
1322 default:
1323 x = border + 2;
1324 break;
1325 case acroFormQuadCenter:
1326 x = border + 2 + 0.5 * (comb - text2->getLength()) * w;
1327 break;
1328 case acroFormQuadRight:
1329 x = border + 2 + (comb - text2->getLength()) * w;
1330 break;
1331 }
1332 y = 0.5 * dy - 0.4 * fontSize;
1333
1334 // set the font matrix
1335 if (tmPos >= 0) {
1336 tok = (GString *)daToks->get(tmPos + 4);
1337 tok->clear();
1338 tok->appendf("{0:.4f}", x);
1339 tok = (GString *)daToks->get(tmPos + 5);
1340 tok->clear();
1341 tok->appendf("{0:.4f}", y);
1342 }
1343
1344 // write the DA string
1345 if (daToks) {
1346 for (i = 0; i < daToks->getLength(); ++i) {
1347 appearBuf->append((GString *)daToks->get(i))->append(' ');
1348 }
1349 }
1350
1351 // write the font matrix (if not part of the DA string)
1352 if (tmPos < 0) {
1353 appearBuf->appendf("1 0 0 1 {0:.4f} {1:.4f} Tm\n", x, y);
1354 }
1355
1356 // write the text string
1357 //~ this should center (instead of left-justify) each character within
1358 //~ its comb cell
1359 for (i = 0; i < text2->getLength(); ++i) {
1360 if (i > 0) {
1361 appearBuf->appendf("{0:.4f} 0 Td\n", w);
1362 }
1363 appearBuf->append('(');
1364 c = text2->getChar(i) & 0xff;
1365 if (c == '(' || c == ')' || c == '\\') {
1366 appearBuf->append('\\');
1367 appearBuf->append(c);
1368 } else if (c < 0x20 || c >= 0x80) {
1369 appearBuf->appendf("{0:.4f} 0 Td\n", w);
1370 } else {
1371 appearBuf->append(c);
1372 }
1373 appearBuf->append(") Tj\n");
1374 }
1375
1376 // regular (non-comb) formatting
1377 } else {
1378
1379 // compute string width
1380 if (font && !font->isCIDFont()) {
1381 w = 0;
1382 for (i = 0; i < text2->getLength(); ++i) {
1383 w += ((Gfx8BitFont *)font)->getWidth(text2->getChar(i));
1384 }
1385 } else {
1386 // otherwise, make a crude estimate
1387 w = text2->getLength() * 0.5;
1388 }
1389
1390 // compute font autosize
1391 if (fontSize == 0) {
1392 fontSize = dy - 2 * border;
1393 fontSize2 = (dx - 4 - 2 * border) / w;
1394 if (fontSize2 < fontSize) {
1395 fontSize = fontSize2;
1396 }
1397 fontSize = floor(fontSize);
1398 if (tfPos >= 0) {
1399 tok = (GString *)daToks->get(tfPos + 1);
1400 tok->clear();
1401 tok->appendf("{0:.4f}", fontSize);
1402 }
1403 }
1404
1405 // compute text start position
1406 w *= fontSize;
1407 switch (quadding) {
1408 case acroFormQuadLeft:
1409 default:
1410 x = border + 2;
1411 break;
1412 case acroFormQuadCenter:
1413 x = (dx - w) / 2;
1414 break;
1415 case acroFormQuadRight:
1416 x = dx - border - 2 - w;
1417 break;
1418 }
1419 y = 0.5 * dy - 0.4 * fontSize;
1420
1421 // set the font matrix
1422 if (tmPos >= 0) {
1423 tok = (GString *)daToks->get(tmPos + 4);
1424 tok->clear();
1425 tok->appendf("{0:.4f}", x);
1426 tok = (GString *)daToks->get(tmPos + 5);
1427 tok->clear();
1428 tok->appendf("{0:.4f}", y);
1429 }
1430
1431 // write the DA string
1432 if (daToks) {
1433 for (i = 0; i < daToks->getLength(); ++i) {
1434 appearBuf->append((GString *)daToks->get(i))->append(' ');
1435 }
1436 }
1437
1438 // write the font matrix (if not part of the DA string)
1439 if (tmPos < 0) {
1440 appearBuf->appendf("1 0 0 1 {0:.4f} {1:.4f} Tm\n", x, y);
1441 }
1442
1443 // write the text string
1444 appearBuf->append('(');
1445 for (i = 0; i < text2->getLength(); ++i) {
1446 c = text2->getChar(i) & 0xff;
1447 if (c == '(' || c == ')' || c == '\\') {
1448 appearBuf->append('\\');
1449 appearBuf->append(c);
1450 } else if (c < 0x20 || c >= 0x80) {
1451 appearBuf->appendf("\\{0:03o}", c);
1452 } else {
1453 appearBuf->append(c);
1454 }
1455 }
1456 appearBuf->append(") Tj\n");
1457 }
1458 }
1459
1460 // cleanup
1461 appearBuf->append("ET\n");
1462 appearBuf->append("Q\n");
1463 if (txField) {
1464 appearBuf->append("EMC\n");
1465 }
1466
1467 if (daToks) {
1468 deleteGList(daToks, GString);
1469 }
1470 if (text2 != text) {
1471 delete text2;
1472 }
1473 }
1474
1475 // Draw the variable text or caption for a field.
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)1476 void AcroFormField::drawListBox(GString **text, GBool *selection,
1477 int nOptions, int topIdx,
1478 GString *da, GfxFontDict *fontDict,
1479 GBool quadding, double xMin, double yMin,
1480 double xMax, double yMax, double border) {
1481 GList *daToks;
1482 GString *tok;
1483 GfxFont *font;
1484 double fontSize, fontSize2, x, y, w, wMax;
1485 int tfPos, tmPos, i, j, c;
1486
1487 //~ if there is no MK entry, this should use the existing content stream,
1488 //~ and only replace the marked content portion of it
1489 //~ (this is only relevant for Tx fields)
1490
1491 // parse the default appearance string
1492 tfPos = tmPos = -1;
1493 if (da) {
1494 daToks = new GList();
1495 i = 0;
1496 while (i < da->getLength()) {
1497 while (i < da->getLength() && Lexer::isSpace(da->getChar(i))) {
1498 ++i;
1499 }
1500 if (i < da->getLength()) {
1501 for (j = i + 1;
1502 j < da->getLength() && !Lexer::isSpace(da->getChar(j));
1503 ++j) ;
1504 daToks->append(new GString(da, i, j - i));
1505 i = j;
1506 }
1507 }
1508 for (i = 2; i < daToks->getLength(); ++i) {
1509 if (i >= 2 && !((GString *)daToks->get(i))->cmp("Tf")) {
1510 tfPos = i - 2;
1511 } else if (i >= 6 && !((GString *)daToks->get(i))->cmp("Tm")) {
1512 tmPos = i - 6;
1513 }
1514 }
1515 } else {
1516 daToks = NULL;
1517 }
1518
1519 // get the font and font size
1520 font = NULL;
1521 fontSize = 0;
1522 if (tfPos >= 0) {
1523 tok = (GString *)daToks->get(tfPos);
1524 if (tok->getLength() >= 1 && tok->getChar(0) == '/') {
1525 if (!fontDict || !(font = fontDict->lookup(tok->getCString() + 1))) {
1526 error(errSyntaxError, -1, "Unknown font in field's DA string");
1527 }
1528 } else {
1529 error(errSyntaxError, -1,
1530 "Invalid font name in 'Tf' operator in field's DA string");
1531 }
1532 tok = (GString *)daToks->get(tfPos + 1);
1533 fontSize = atof(tok->getCString());
1534 } else {
1535 error(errSyntaxError, -1, "Missing 'Tf' operator in field's DA string");
1536 }
1537
1538 // compute font autosize
1539 if (fontSize == 0) {
1540 wMax = 0;
1541 for (i = 0; i < nOptions; ++i) {
1542 if (font && !font->isCIDFont()) {
1543 w = 0;
1544 for (j = 0; j < text[i]->getLength(); ++j) {
1545 w += ((Gfx8BitFont *)font)->getWidth(text[i]->getChar(j));
1546 }
1547 } else {
1548 // otherwise, make a crude estimate
1549 w = text[i]->getLength() * 0.5;
1550 }
1551 if (w > wMax) {
1552 wMax = w;
1553 }
1554 }
1555 fontSize = yMax - yMin - 2 * border;
1556 fontSize2 = (xMax - xMin - 4 - 2 * border) / wMax;
1557 if (fontSize2 < fontSize) {
1558 fontSize = fontSize2;
1559 }
1560 fontSize = floor(fontSize);
1561 if (tfPos >= 0) {
1562 tok = (GString *)daToks->get(tfPos + 1);
1563 tok->clear();
1564 tok->appendf("{0:.4f}", fontSize);
1565 }
1566 }
1567
1568 // draw the text
1569 y = yMax - yMin - 1.1 * fontSize;
1570 for (i = topIdx; i < nOptions; ++i) {
1571
1572 // setup
1573 appearBuf->append("q\n");
1574
1575 // draw the background if selected
1576 if (selection[i]) {
1577 appearBuf->append("0 g f\n");
1578 appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} re f\n",
1579 border,
1580 y - 0.2 * fontSize,
1581 xMax - xMin - 2 * border,
1582 1.1 * fontSize);
1583 }
1584
1585 // setup
1586 appearBuf->append("BT\n");
1587
1588 // compute string width
1589 if (font && !font->isCIDFont()) {
1590 w = 0;
1591 for (j = 0; j < text[i]->getLength(); ++j) {
1592 w += ((Gfx8BitFont *)font)->getWidth(text[i]->getChar(j));
1593 }
1594 } else {
1595 // otherwise, make a crude estimate
1596 w = text[i]->getLength() * 0.5;
1597 }
1598
1599 // compute text start position
1600 w *= fontSize;
1601 switch (quadding) {
1602 case acroFormQuadLeft:
1603 default:
1604 x = border + 2;
1605 break;
1606 case acroFormQuadCenter:
1607 x = (xMax - xMin - w) / 2;
1608 break;
1609 case acroFormQuadRight:
1610 x = xMax - xMin - border - 2 - w;
1611 break;
1612 }
1613
1614 // set the font matrix
1615 if (tmPos >= 0) {
1616 tok = (GString *)daToks->get(tmPos + 4);
1617 tok->clear();
1618 tok->appendf("{0:.4f}", x);
1619 tok = (GString *)daToks->get(tmPos + 5);
1620 tok->clear();
1621 tok->appendf("{0:.4f}", y);
1622 }
1623
1624 // write the DA string
1625 if (daToks) {
1626 for (j = 0; j < daToks->getLength(); ++j) {
1627 appearBuf->append((GString *)daToks->get(j))->append(' ');
1628 }
1629 }
1630
1631 // write the font matrix (if not part of the DA string)
1632 if (tmPos < 0) {
1633 appearBuf->appendf("1 0 0 1 {0:.4f} {1:.4f} Tm\n", x, y);
1634 }
1635
1636 // change the text color if selected
1637 if (selection[i]) {
1638 appearBuf->append("1 g\n");
1639 }
1640
1641 // write the text string
1642 appearBuf->append('(');
1643 for (j = 0; j < text[i]->getLength(); ++j) {
1644 c = text[i]->getChar(j) & 0xff;
1645 if (c == '(' || c == ')' || c == '\\') {
1646 appearBuf->append('\\');
1647 appearBuf->append(c);
1648 } else if (c < 0x20 || c >= 0x80) {
1649 appearBuf->appendf("\\{0:03o}", c);
1650 } else {
1651 appearBuf->append(c);
1652 }
1653 }
1654 appearBuf->append(") Tj\n");
1655
1656 // cleanup
1657 appearBuf->append("ET\n");
1658 appearBuf->append("Q\n");
1659
1660 // next line
1661 y -= 1.1 * fontSize;
1662 }
1663
1664 if (daToks) {
1665 deleteGList(daToks, GString);
1666 }
1667 }
1668
1669 // Figure out how much text will fit on the next line. Returns:
1670 // *end = one past the last character to be included
1671 // *width = width of the characters start .. end-1
1672 // *next = index of first character on the following line
getNextLine(GString * text,int start,GfxFont * font,double fontSize,double wMax,int * end,double * width,int * next)1673 void AcroFormField::getNextLine(GString *text, int start,
1674 GfxFont *font, double fontSize, double wMax,
1675 int *end, double *width, int *next) {
1676 double w, dw;
1677 int j, k, c;
1678
1679 // figure out how much text will fit on the line
1680 //~ what does Adobe do with tabs?
1681 w = 0;
1682 for (j = start; j < text->getLength() && w <= wMax; ++j) {
1683 c = text->getChar(j) & 0xff;
1684 if (c == 0x0a || c == 0x0d) {
1685 break;
1686 }
1687 if (font && !font->isCIDFont()) {
1688 dw = ((Gfx8BitFont *)font)->getWidth(c) * fontSize;
1689 } else {
1690 // otherwise, make a crude estimate
1691 dw = 0.5 * fontSize;
1692 }
1693 w += dw;
1694 }
1695 if (w > wMax) {
1696 for (k = j; k > start && text->getChar(k-1) != ' '; --k) ;
1697 for (; k > start && text->getChar(k-1) == ' '; --k) ;
1698 if (k > start) {
1699 j = k;
1700 }
1701 if (j == start) {
1702 // handle the pathological case where the first character is
1703 // too wide to fit on the line all by itself
1704 j = start + 1;
1705 }
1706 }
1707 *end = j;
1708
1709 // compute the width
1710 w = 0;
1711 for (k = start; k < j; ++k) {
1712 if (font && !font->isCIDFont()) {
1713 dw = ((Gfx8BitFont *)font)->getWidth(text->getChar(k)) * fontSize;
1714 } else {
1715 // otherwise, make a crude estimate
1716 dw = 0.5 * fontSize;
1717 }
1718 w += dw;
1719 }
1720 *width = w;
1721
1722 // next line
1723 while (j < text->getLength() && text->getChar(j) == ' ') {
1724 ++j;
1725 }
1726 if (j < text->getLength() && text->getChar(j) == 0x0d) {
1727 ++j;
1728 }
1729 if (j < text->getLength() && text->getChar(j) == 0x0a) {
1730 ++j;
1731 }
1732 *next = j;
1733 }
1734
1735 // Draw an (approximate) circle of radius <r> centered at (<cx>, <cy>).
1736 // <cmd> is used to draw the circle ("f", "s", or "b").
drawCircle(double cx,double cy,double r,const char * cmd)1737 void AcroFormField::drawCircle(double cx, double cy, double r,
1738 const char *cmd) {
1739 appearBuf->appendf("{0:.4f} {1:.4f} m\n",
1740 cx + r, cy);
1741 appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n",
1742 cx + r, cy + bezierCircle * r,
1743 cx + bezierCircle * r, cy + r,
1744 cx, cy + r);
1745 appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n",
1746 cx - bezierCircle * r, cy + r,
1747 cx - r, cy + bezierCircle * r,
1748 cx - r, cy);
1749 appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n",
1750 cx - r, cy - bezierCircle * r,
1751 cx - bezierCircle * r, cy - r,
1752 cx, cy - r);
1753 appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n",
1754 cx + bezierCircle * r, cy - r,
1755 cx + r, cy - bezierCircle * r,
1756 cx + r, cy);
1757 appearBuf->appendf("{0:s}\n", cmd);
1758 }
1759
1760 // Draw the top-left half of an (approximate) circle of radius <r>
1761 // centered at (<cx>, <cy>).
drawCircleTopLeft(double cx,double cy,double r)1762 void AcroFormField::drawCircleTopLeft(double cx, double cy, double r) {
1763 double r2;
1764
1765 r2 = r / sqrt(2.0);
1766 appearBuf->appendf("{0:.4f} {1:.4f} m\n",
1767 cx + r2, cy + r2);
1768 appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n",
1769 cx + (1 - bezierCircle) * r2,
1770 cy + (1 + bezierCircle) * r2,
1771 cx - (1 - bezierCircle) * r2,
1772 cy + (1 + bezierCircle) * r2,
1773 cx - r2,
1774 cy + r2);
1775 appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n",
1776 cx - (1 + bezierCircle) * r2,
1777 cy + (1 - bezierCircle) * r2,
1778 cx - (1 + bezierCircle) * r2,
1779 cy - (1 - bezierCircle) * r2,
1780 cx - r2,
1781 cy - r2);
1782 appearBuf->append("S\n");
1783 }
1784
1785 // Draw the bottom-right half of an (approximate) circle of radius <r>
1786 // centered at (<cx>, <cy>).
drawCircleBottomRight(double cx,double cy,double r)1787 void AcroFormField::drawCircleBottomRight(double cx, double cy, double r) {
1788 double r2;
1789
1790 r2 = r / sqrt(2.0);
1791 appearBuf->appendf("{0:.4f} {1:.4f} m\n",
1792 cx - r2, cy - r2);
1793 appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n",
1794 cx - (1 - bezierCircle) * r2,
1795 cy - (1 + bezierCircle) * r2,
1796 cx + (1 - bezierCircle) * r2,
1797 cy - (1 + bezierCircle) * r2,
1798 cx + r2,
1799 cy - r2);
1800 appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n",
1801 cx + (1 + bezierCircle) * r2,
1802 cy - (1 - bezierCircle) * r2,
1803 cx + (1 + bezierCircle) * r2,
1804 cy + (1 - bezierCircle) * r2,
1805 cx + r2,
1806 cy + r2);
1807 appearBuf->append("S\n");
1808 }
1809
getResources(Object * res)1810 Object *AcroFormField::getResources(Object *res) {
1811 Object kidsObj, annotObj, obj1;
1812 int i;
1813
1814 if (acroForm->needAppearances) {
1815 fieldLookup("DR", res);
1816 } else {
1817 res->initArray(acroForm->doc->getXRef());
1818 // find the annotation object(s)
1819 if (fieldObj.dictLookup("Kids", &kidsObj)->isArray()) {
1820 for (i = 0; i < kidsObj.arrayGetLength(); ++i) {
1821 kidsObj.arrayGet(i, &annotObj);
1822 if (annotObj.isDict()) {
1823 if (getAnnotResources(annotObj.getDict(), &obj1)->isDict()) {
1824 res->arrayAdd(&obj1);
1825 } else {
1826 obj1.free();
1827 }
1828 }
1829 annotObj.free();
1830 }
1831 } else {
1832 if (getAnnotResources(fieldObj.getDict(), &obj1)->isDict()) {
1833 res->arrayAdd(&obj1);
1834 } else {
1835 obj1.free();
1836 }
1837 }
1838 kidsObj.free();
1839 }
1840
1841 return res;
1842 }
1843
getAnnotResources(Dict * annot,Object * res)1844 Object *AcroFormField::getAnnotResources(Dict *annot, Object *res) {
1845 Object apObj, asObj, appearance, obj1;
1846
1847 // get the appearance stream
1848 if (annot->lookup("AP", &apObj)->isDict()) {
1849 apObj.dictLookup("N", &obj1);
1850 if (obj1.isDict()) {
1851 if (annot->lookup("AS", &asObj)->isName()) {
1852 obj1.dictLookup(asObj.getName(), &appearance);
1853 } else if (obj1.dictGetLength() == 1) {
1854 obj1.dictGetVal(0, &appearance);
1855 } else {
1856 obj1.dictLookup("Off", &appearance);
1857 }
1858 asObj.free();
1859 } else {
1860 obj1.copy(&appearance);
1861 }
1862 obj1.free();
1863 }
1864 apObj.free();
1865
1866 if (appearance.isStream()) {
1867 appearance.streamGetDict()->lookup("Resources", res);
1868 } else {
1869 res->initNull();
1870 }
1871 appearance.free();
1872
1873 return res;
1874 }
1875
1876 // Look up an inheritable field dictionary entry.
fieldLookup(const char * key,Object * obj)1877 Object *AcroFormField::fieldLookup(const char *key, Object *obj) {
1878 return fieldLookup(fieldObj.getDict(), key, obj);
1879 }
1880
fieldLookup(Dict * dict,const char * key,Object * obj)1881 Object *AcroFormField::fieldLookup(Dict *dict, const char *key, Object *obj) {
1882 Object parent;
1883
1884 if (!dict->lookup(key, obj)->isNull()) {
1885 return obj;
1886 }
1887 obj->free();
1888 if (dict->lookup("Parent", &parent)->isDict()) {
1889 fieldLookup(parent.getDict(), key, obj);
1890 } else {
1891 // some fields don't specify a parent, so we check the AcroForm
1892 // dictionary just in case
1893 acroForm->acroFormObj.dictLookup(key, obj);
1894 }
1895 parent.free();
1896 return obj;
1897 }
1898