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