1
//========================================================================
5
// Copyright 2000-2003 Glyph & Cog, LLC
7
//========================================================================
11
#ifdef USE_GCC_PRAGMAS
12
#pragma implementation
27
//------------------------------------------------------------------------
29
#define annotFlagHidden 0x0002
30
#define annotFlagPrint 0x0004
31
#define annotFlagNoView 0x0020
33
#define fieldFlagReadOnly 0x00000001
34
#define fieldFlagRequired 0x00000002
35
#define fieldFlagNoExport 0x00000004
36
#define fieldFlagMultiline 0x00001000
37
#define fieldFlagPassword 0x00002000
38
#define fieldFlagNoToggleToOff 0x00004000
39
#define fieldFlagRadio 0x00008000
40
#define fieldFlagPushbutton 0x00010000
41
#define fieldFlagCombo 0x00020000
42
#define fieldFlagEdit 0x00040000
43
#define fieldFlagSort 0x00080000
44
#define fieldFlagFileSelect 0x00100000
45
#define fieldFlagMultiSelect 0x00200000
46
#define fieldFlagDoNotSpellCheck 0x00400000
47
#define fieldFlagDoNotScroll 0x00800000
48
#define fieldFlagComb 0x01000000
49
#define fieldFlagRichText 0x02000000
50
#define fieldFlagRadiosInUnison 0x02000000
51
#define fieldFlagCommitOnSelChange 0x04000000
53
#define fieldQuadLeft 0
54
#define fieldQuadCenter 1
55
#define fieldQuadRight 2
57
// distance of Bezier control point from center for circle approximation
58
// = (4 * (sqrt(2) - 1) / 3) * r
59
#define bezierCircle 0.55228475
61
//------------------------------------------------------------------------
63
//------------------------------------------------------------------------
65
AnnotBorderStyle::AnnotBorderStyle(AnnotBorderType typeA, double widthA,
66
double *dashA, int dashLengthA,
67
double rA, double gA, double bA) {
71
dashLength = dashLengthA;
77
AnnotBorderStyle::~AnnotBorderStyle() {
83
//------------------------------------------------------------------------
85
//------------------------------------------------------------------------
87
Annot::Annot(XRef *xrefA, Dict *acroForm, Dict *dict, Ref *refA) {
88
Object apObj, asObj, obj1, obj2, obj3;
89
AnnotBorderType borderType;
93
double borderR, borderG, borderB;
104
//----- parse the type
106
if (dict->lookup("Subtype", &obj1)->isName()) {
107
type = new GString(obj1.getName());
111
//----- parse the rectangle
113
if (dict->lookup("Rect", &obj1)->isArray() &&
114
obj1.arrayGetLength() == 4) {
115
xMin = yMin = xMax = yMax = 0;
116
if (obj1.arrayGet(0, &obj2)->isNum()) {
117
xMin = obj2.getNum();
120
if (obj1.arrayGet(1, &obj2)->isNum()) {
121
yMin = obj2.getNum();
124
if (obj1.arrayGet(2, &obj2)->isNum()) {
125
xMax = obj2.getNum();
128
if (obj1.arrayGet(3, &obj2)->isNum()) {
129
yMax = obj2.getNum();
133
t = xMin; xMin = xMax; xMax = t;
136
t = yMin; yMin = yMax; yMax = t;
139
error(-1, "Bad bounding box for annotation");
144
//----- parse the flags
146
if (dict->lookup("F", &obj1)->isInt()) {
147
flags = obj1.getInt();
153
//----- parse the border style
155
borderType = annotBorderSolid;
158
borderDashLength = 0;
162
if (dict->lookup("BS", &obj1)->isDict()) {
163
if (obj1.dictLookup("S", &obj2)->isName()) {
164
if (obj2.isName("S")) {
165
borderType = annotBorderSolid;
166
} else if (obj2.isName("D")) {
167
borderType = annotBorderDashed;
168
} else if (obj2.isName("B")) {
169
borderType = annotBorderBeveled;
170
} else if (obj2.isName("I")) {
171
borderType = annotBorderInset;
172
} else if (obj2.isName("U")) {
173
borderType = annotBorderUnderlined;
177
if (obj1.dictLookup("W", &obj2)->isNum()) {
178
borderWidth = obj2.getNum();
181
if (obj1.dictLookup("D", &obj2)->isArray()) {
182
borderDashLength = obj2.arrayGetLength();
183
borderDash = (double *)gmallocn(borderDashLength, sizeof(double));
184
for (i = 0; i < borderDashLength; ++i) {
185
if (obj2.arrayGet(i, &obj3)->isNum()) {
186
borderDash[i] = obj3.getNum();
196
if (dict->lookup("Border", &obj1)->isArray()) {
197
if (obj1.arrayGetLength() >= 3) {
198
if (obj1.arrayGet(2, &obj2)->isNum()) {
199
borderWidth = obj2.getNum();
202
if (obj1.arrayGetLength() >= 4) {
203
if (obj1.arrayGet(3, &obj2)->isArray()) {
204
borderType = annotBorderDashed;
205
borderDashLength = obj2.arrayGetLength();
206
borderDash = (double *)gmallocn(borderDashLength, sizeof(double));
207
for (i = 0; i < borderDashLength; ++i) {
208
if (obj2.arrayGet(i, &obj3)->isNum()) {
209
borderDash[i] = obj3.getNum();
216
// Adobe draws no border at all if the last element is of
226
if (dict->lookup("C", &obj1)->isArray() && obj1.arrayGetLength() == 3) {
227
if (obj1.arrayGet(0, &obj2)->isNum()) {
228
borderR = obj2.getNum();
231
if (obj1.arrayGet(1, &obj2)->isNum()) {
232
borderG = obj2.getNum();
235
if (obj1.arrayGet(2, &obj2)->isNum()) {
236
borderB = obj2.getNum();
241
borderStyle = new AnnotBorderStyle(borderType, borderWidth,
242
borderDash, borderDashLength,
243
borderR, borderG, borderB);
245
//----- get the annotation appearance
247
if (dict->lookup("AP", &apObj)->isDict()) {
248
if (dict->lookup("AS", &asObj)->isName()) {
249
if (apObj.dictLookup("N", &obj1)->isDict()) {
250
if (obj1.dictLookupNF(asObj.getName(), &obj2)->isRef()) {
251
obj2.copy(&appearance);
255
if (obj1.dictLookupNF("Off", &obj2)->isRef()) {
256
obj2.copy(&appearance);
263
if (apObj.dictLookupNF("N", &obj1)->isRef()) {
264
obj1.copy(&appearance);
286
void Annot::generateFieldAppearance(Dict *field, Dict *annot, Dict *acroForm) {
287
Object mkObj, ftObj, appearDict, drObj, obj1, obj2, obj3;
289
MemStream *appearStream;
290
GfxFontDict *fontDict;
294
GString *caption, *da;
297
int dashLength, ff, quadding, comb, nOptions, topIdx, i, j;
299
// must be a Widget annotation
300
if (type->cmp("Widget")) {
304
appearBuf = new GString();
306
// get the appearance characteristics (MK) dictionary
307
if (annot->lookup("MK", &mkObj)->isDict()) {
308
mkDict = mkObj.getDict();
313
// draw the background
315
if (mkDict->lookup("BG", &obj1)->isArray() &&
316
obj1.arrayGetLength() > 0) {
317
setColor(obj1.getArray(), gTrue, 0);
318
appearBuf->appendf("0 0 {0:.2f} {1:.2f} re f\n",
319
xMax - xMin, yMax - yMin);
324
// get the field type
325
fieldLookup(field, "FT", &ftObj);
327
// get the field flags (Ff) value
328
if (fieldLookup(field, "Ff", &obj1)->isInt()) {
337
w = borderStyle->getWidth();
339
mkDict->lookup("BC", &obj1);
340
if (!(obj1.isArray() && obj1.arrayGetLength() > 0)) {
341
mkDict->lookup("BG", &obj1);
343
if (obj1.isArray() && obj1.arrayGetLength() > 0) {
347
// radio buttons with no caption have a round border
348
hasCaption = mkDict->lookup("CA", &obj2)->isString();
350
if (ftObj.isName("Btn") && (ff & fieldFlagRadio) && !hasCaption) {
351
r = 0.5 * (dx < dy ? dx : dy);
352
switch (borderStyle->getType()) {
353
case annotBorderDashed:
354
appearBuf->append("[");
355
borderStyle->getDash(&dash, &dashLength);
356
for (i = 0; i < dashLength; ++i) {
357
appearBuf->appendf(" {0:.2f}", dash[i]);
359
appearBuf->append("] 0 d\n");
360
// fall through to the solid case
361
case annotBorderSolid:
362
case annotBorderUnderlined:
363
appearBuf->appendf("{0:.2f} w\n", w);
364
setColor(obj1.getArray(), gFalse, 0);
365
drawCircle(0.5 * dx, 0.5 * dy, r - 0.5 * w, gFalse);
367
case annotBorderBeveled:
368
case annotBorderInset:
369
appearBuf->appendf("{0:.2f} w\n", 0.5 * w);
370
setColor(obj1.getArray(), gFalse, 0);
371
drawCircle(0.5 * dx, 0.5 * dy, r - 0.25 * w, gFalse);
372
setColor(obj1.getArray(), gFalse,
373
borderStyle->getType() == annotBorderBeveled ? 1 : -1);
374
drawCircleTopLeft(0.5 * dx, 0.5 * dy, r - 0.75 * w);
375
setColor(obj1.getArray(), gFalse,
376
borderStyle->getType() == annotBorderBeveled ? -1 : 1);
377
drawCircleBottomRight(0.5 * dx, 0.5 * dy, r - 0.75 * w);
382
switch (borderStyle->getType()) {
383
case annotBorderDashed:
384
appearBuf->append("[");
385
borderStyle->getDash(&dash, &dashLength);
386
for (i = 0; i < dashLength; ++i) {
387
appearBuf->appendf(" {0:.2f}", dash[i]);
389
appearBuf->append("] 0 d\n");
390
// fall through to the solid case
391
case annotBorderSolid:
392
appearBuf->appendf("{0:.2f} w\n", w);
393
setColor(obj1.getArray(), gFalse, 0);
394
appearBuf->appendf("{0:.2f} {0:.2f} {1:.2f} {2:.2f} re s\n",
395
0.5 * w, dx - w, dy - w);
397
case annotBorderBeveled:
398
case annotBorderInset:
399
setColor(obj1.getArray(), gTrue,
400
borderStyle->getType() == annotBorderBeveled ? 1 : -1);
401
appearBuf->append("0 0 m\n");
402
appearBuf->appendf("0 {0:.2f} l\n", dy);
403
appearBuf->appendf("{0:.2f} {1:.2f} l\n", dx, dy);
404
appearBuf->appendf("{0:.2f} {1:.2f} l\n", dx - w, dy - w);
405
appearBuf->appendf("{0:.2f} {1:.2f} l\n", w, dy - w);
406
appearBuf->appendf("{0:.2f} {0:.2f} l\n", w);
407
appearBuf->append("f\n");
408
setColor(obj1.getArray(), gTrue,
409
borderStyle->getType() == annotBorderBeveled ? -1 : 1);
410
appearBuf->append("0 0 m\n");
411
appearBuf->appendf("{0:.2f} 0 l\n", dx);
412
appearBuf->appendf("{0:.2f} {1:.2f} l\n", dx, dy);
413
appearBuf->appendf("{0:.2f} {1:.2f} l\n", dx - w, dy - w);
414
appearBuf->appendf("{0:.2f} {1:.2f} l\n", dx - w, w);
415
appearBuf->appendf("{0:.2f} {0:.2f} l\n", w);
416
appearBuf->append("f\n");
418
case annotBorderUnderlined:
419
appearBuf->appendf("{0:.2f} w\n", w);
420
setColor(obj1.getArray(), gFalse, 0);
421
appearBuf->appendf("0 0 m {0:.2f} 0 l s\n", dx);
425
// clip to the inside of the border
426
appearBuf->appendf("{0:.2f} {0:.2f} {1:.2f} {2:.2f} re W n\n",
427
w, dx - 2 * w, dy - 2 * w);
434
// get the resource dictionary
435
acroForm->lookup("DR", &drObj);
437
// build the font dictionary
438
if (drObj.isDict() && drObj.dictLookup("Font", &obj1)->isDict()) {
439
fontDict = new GfxFontDict(xref, NULL, obj1.getDict());
445
// get the default appearance string
446
if (fieldLookup(field, "DA", &obj1)->isNull()) {
448
acroForm->lookup("DA", &obj1);
450
if (obj1.isString()) {
451
da = obj1.getString()->copy();
457
// draw the field contents
458
if (ftObj.isName("Btn")) {
461
if (mkDict->lookup("CA", &obj1)->isString()) {
462
caption = obj1.getString()->copy();
467
if (ff & fieldFlagRadio) {
468
//~ Acrobat doesn't draw a caption if there is no AP dict (?)
469
if (fieldLookup(field, "V", &obj1)->isName()) {
470
if (annot->lookup("AS", &obj2)->isName(obj1.getName())) {
472
drawText(caption, da, fontDict, gFalse, 0, fieldQuadCenter,
476
if (mkDict->lookup("BC", &obj3)->isArray() &&
477
obj3.arrayGetLength() > 0) {
480
setColor(obj3.getArray(), gTrue, 0);
481
drawCircle(0.5 * dx, 0.5 * dy, 0.2 * (dx < dy ? dx : dy),
492
} else if (ff & fieldFlagPushbutton) {
494
drawText(caption, da, fontDict, gFalse, 0, fieldQuadCenter,
499
// According to the PDF spec the off state must be named "Off",
500
// and the on state can be named anything, but Acrobat apparently
501
// looks for "Yes" and treats anything else as off.
502
if (fieldLookup(field, "V", &obj1)->isName("Yes")) {
504
caption = new GString("3"); // ZapfDingbats checkmark
506
drawText(caption, da, fontDict, gFalse, 0, fieldQuadCenter,
514
} else if (ftObj.isName("Tx")) {
515
//~ value strings can be Unicode
516
if (fieldLookup(field, "V", &obj1)->isString()) {
517
if (fieldLookup(field, "Q", &obj2)->isInt()) {
518
quadding = obj2.getInt();
520
quadding = fieldQuadLeft;
524
if (ff & fieldFlagComb) {
525
if (fieldLookup(field, "MaxLen", &obj2)->isInt()) {
526
comb = obj2.getInt();
530
drawText(obj1.getString(), da, fontDict,
531
ff & fieldFlagMultiline, comb, quadding, gTrue, gFalse);
534
} else if (ftObj.isName("Ch")) {
535
//~ value/option strings can be Unicode
536
if (fieldLookup(field, "Q", &obj1)->isInt()) {
537
quadding = obj1.getInt();
539
quadding = fieldQuadLeft;
543
if (ff & fieldFlagCombo) {
544
if (fieldLookup(field, "V", &obj1)->isString()) {
545
drawText(obj1.getString(), da, fontDict,
546
gFalse, 0, quadding, gTrue, gFalse);
547
//~ Acrobat draws a popup icon on the right side
552
if (field->lookup("Opt", &obj1)->isArray()) {
553
nOptions = obj1.arrayGetLength();
554
// get the option text
555
text = (GString **)gmallocn(nOptions, sizeof(GString *));
556
for (i = 0; i < nOptions; ++i) {
558
obj1.arrayGet(i, &obj2);
559
if (obj2.isString()) {
560
text[i] = obj2.getString()->copy();
561
} else if (obj2.isArray() && obj2.arrayGetLength() == 2) {
562
if (obj2.arrayGet(1, &obj3)->isString()) {
563
text[i] = obj3.getString()->copy();
569
text[i] = new GString();
572
// get the selected option(s)
573
selection = (GBool *)gmallocn(nOptions, sizeof(GBool));
574
//~ need to use the I field in addition to the V field
575
fieldLookup(field, "V", &obj2);
576
for (i = 0; i < nOptions; ++i) {
577
selection[i] = gFalse;
578
if (obj2.isString()) {
579
if (!obj2.getString()->cmp(text[i])) {
580
selection[i] = gTrue;
582
} else if (obj2.isArray()) {
583
for (j = 0; j < obj2.arrayGetLength(); ++j) {
584
if (obj2.arrayGet(j, &obj3)->isString() &&
585
!obj3.getString()->cmp(text[i])) {
586
selection[i] = gTrue;
594
if (field->lookup("TI", &obj2)->isInt()) {
595
topIdx = obj2.getInt();
601
drawListBox(text, selection, nOptions, topIdx, da, fontDict, quadding);
602
for (i = 0; i < nOptions; ++i) {
610
} else if (ftObj.isName("Sig")) {
613
error(-1, "Unknown field type");
620
// build the appearance stream dictionary
621
appearDict.initDict(xref);
622
appearDict.dictAdd(copyString("Length"),
623
obj1.initInt(appearBuf->getLength()));
624
appearDict.dictAdd(copyString("Subtype"), obj1.initName("Form"));
625
obj1.initArray(xref);
626
obj1.arrayAdd(obj2.initReal(0));
627
obj1.arrayAdd(obj2.initReal(0));
628
obj1.arrayAdd(obj2.initReal(xMax - xMin));
629
obj1.arrayAdd(obj2.initReal(yMax - yMin));
630
appearDict.dictAdd(copyString("BBox"), &obj1);
632
// set the resource dictionary
633
if (drObj.isDict()) {
634
appearDict.dictAdd(copyString("Resources"), drObj.copy(&obj1));
638
// build the appearance stream
639
appearStream = new MemStream(appearBuf->getCString(), 0,
640
appearBuf->getLength(), &appearDict);
642
appearance.initStream(appearStream);
651
// Set the current fill or stroke color, based on <a> (which should
652
// have 1, 3, or 4 elements). If <adjust> is +1, color is brightened;
653
// if <adjust> is -1, color is darkened; otherwise color is not
655
void Annot::setColor(Array *a, GBool fill, int adjust) {
660
nComps = a->getLength();
664
for (i = 0; i < nComps && i < 4; ++i) {
665
if (a->get(i, &obj1)->isNum()) {
666
color[i] = obj1.getNum();
676
for (i = 0; i < nComps; ++i) {
677
color[i] = 0.5 * color[i] + 0.5;
679
} else if (adjust < 0) {
680
for (i = 0; i < nComps; ++i) {
681
color[i] = 0.5 * color[i];
685
appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:c}\n",
686
color[0], color[1], color[2], color[3],
688
} else if (nComps == 3) {
689
appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:s}\n",
690
color[0], color[1], color[2],
693
appearBuf->appendf("{0:.2f} {1:c}\n",
699
// Draw the variable text or caption for a field.
700
void Annot::drawText(GString *text, GString *da, GfxFontDict *fontDict,
701
GBool multiline, int comb, int quadding,
702
GBool txField, GBool forceZapfDingbats) {
706
double fontSize, fontSize2, border, x, xPrev, y, w, w2, wMax;
707
int tfPos, tmPos, i, j, k, c;
709
//~ if there is no MK entry, this should use the existing content stream,
710
//~ and only replace the marked content portion of it
711
//~ (this is only relevant for Tx fields)
713
// parse the default appearance string
716
daToks = new GList();
718
while (i < da->getLength()) {
719
while (i < da->getLength() && Lexer::isSpace(da->getChar(i))) {
722
if (i < da->getLength()) {
724
j < da->getLength() && !Lexer::isSpace(da->getChar(j));
726
daToks->append(new GString(da, i, j - i));
730
for (i = 2; i < daToks->getLength(); ++i) {
731
if (i >= 2 && !((GString *)daToks->get(i))->cmp("Tf")) {
733
} else if (i >= 6 && !((GString *)daToks->get(i))->cmp("Tm")) {
741
// force ZapfDingbats
742
//~ this should create the font if needed (?)
743
if (forceZapfDingbats) {
745
tok = (GString *)daToks->get(tfPos);
746
if (tok->cmp("/ZaDb")) {
748
tok->append("/ZaDb");
753
// get the font and font size
757
tok = (GString *)daToks->get(tfPos);
758
if (tok->getLength() >= 1 && tok->getChar(0) == '/') {
759
if (!fontDict || !(font = fontDict->lookup(tok->getCString() + 1))) {
760
error(-1, "Unknown font in field's DA string");
763
error(-1, "Invalid font name in 'Tf' operator in field's DA string");
765
tok = (GString *)daToks->get(tfPos + 1);
766
fontSize = atof(tok->getCString());
768
error(-1, "Missing 'Tf' operator in field's DA string");
771
// get the border width
772
border = borderStyle->getWidth();
776
appearBuf->append("/Tx BMC\n");
778
appearBuf->append("q\n");
779
appearBuf->append("BT\n");
783
// note: the comb flag is ignored in multiline mode
785
wMax = xMax - xMin - 2 * border - 4;
787
// compute font autosize
789
for (fontSize = 20; fontSize > 1; --fontSize) {
793
while (i < text->getLength()) {
794
getNextLine(text, i, font, fontSize, wMax, &j, &w, &k);
801
// approximate the descender for the last line
802
if (y >= 0.33 * fontSize) {
807
tok = (GString *)daToks->get(tfPos + 1);
809
tok->appendf("{0:.2f}", fontSize);
813
// starting y coordinate
814
// (note: each line of text starts with a Td operator that moves
818
// set the font matrix
820
tok = (GString *)daToks->get(tmPos + 4);
823
tok = (GString *)daToks->get(tmPos + 5);
825
tok->appendf("{0:.2f}", y);
828
// write the DA string
830
for (i = 0; i < daToks->getLength(); ++i) {
831
appearBuf->append((GString *)daToks->get(i))->append(' ');
835
// write the font matrix (if not part of the DA string)
837
appearBuf->appendf("1 0 0 1 0 {0:.2f} Tm\n", y);
840
// write a series of lines of text
843
while (i < text->getLength()) {
845
getNextLine(text, i, font, fontSize, wMax, &j, &w, &k);
847
// compute text start position
853
case fieldQuadCenter:
854
x = (xMax - xMin - w) / 2;
857
x = xMax - xMin - border - 2 - w;
862
appearBuf->appendf("{0:.2f} {1:.2f} Td\n", x - xPrev, -fontSize);
863
appearBuf->append('(');
865
c = text->getChar(i) & 0xff;
866
if (c == '(' || c == ')' || c == '\\') {
867
appearBuf->append('\\');
868
appearBuf->append(c);
869
} else if (c < 0x20 || c >= 0x80) {
870
appearBuf->appendf("\\{0:03o}", c);
872
appearBuf->append(c);
875
appearBuf->append(") Tj\n");
884
//~ replace newlines with spaces? - what does Acrobat do?
889
// compute comb spacing
890
w = (xMax - xMin - 2 * border) / comb;
892
// compute font autosize
894
fontSize = yMax - yMin - 2 * border;
898
fontSize = floor(fontSize);
900
tok = (GString *)daToks->get(tfPos + 1);
902
tok->appendf("{0:.2f}", fontSize);
906
// compute text start position
912
case fieldQuadCenter:
913
x = border + 2 + 0.5 * (comb - text->getLength()) * w;
916
x = border + 2 + (comb - text->getLength()) * w;
919
y = 0.5 * (yMax - yMin) - 0.4 * fontSize;
921
// set the font matrix
923
tok = (GString *)daToks->get(tmPos + 4);
925
tok->appendf("{0:.2f}", x);
926
tok = (GString *)daToks->get(tmPos + 5);
928
tok->appendf("{0:.2f}", y);
931
// write the DA string
933
for (i = 0; i < daToks->getLength(); ++i) {
934
appearBuf->append((GString *)daToks->get(i))->append(' ');
938
// write the font matrix (if not part of the DA string)
940
appearBuf->appendf("1 0 0 1 {0:.2f} {1:.2f} Tm\n", x, y);
943
// write the text string
944
//~ this should center (instead of left-justify) each character within
946
for (i = 0; i < text->getLength(); ++i) {
948
appearBuf->appendf("{0:.2f} 0 Td\n", w);
950
appearBuf->append('(');
951
c = text->getChar(i) & 0xff;
952
if (c == '(' || c == ')' || c == '\\') {
953
appearBuf->append('\\');
954
appearBuf->append(c);
955
} else if (c < 0x20 || c >= 0x80) {
956
appearBuf->appendf("{0:.2f} 0 Td\n", w);
958
appearBuf->append(c);
960
appearBuf->append(") Tj\n");
963
// regular (non-comb) formatting
966
// compute string width
967
if (font && !font->isCIDFont()) {
969
for (i = 0; i < text->getLength(); ++i) {
970
w += ((Gfx8BitFont *)font)->getWidth(text->getChar(i));
973
// otherwise, make a crude estimate
974
w = text->getLength() * 0.5;
977
// compute font autosize
979
fontSize = yMax - yMin - 2 * border;
980
fontSize2 = (xMax - xMin - 4 - 2 * border) / w;
981
if (fontSize2 < fontSize) {
982
fontSize = fontSize2;
984
fontSize = floor(fontSize);
986
tok = (GString *)daToks->get(tfPos + 1);
988
tok->appendf("{0:.2f}", fontSize);
992
// compute text start position
999
case fieldQuadCenter:
1000
x = (xMax - xMin - w) / 2;
1002
case fieldQuadRight:
1003
x = xMax - xMin - border - 2 - w;
1006
y = 0.5 * (yMax - yMin) - 0.4 * fontSize;
1008
// set the font matrix
1010
tok = (GString *)daToks->get(tmPos + 4);
1012
tok->appendf("{0:.2f}", x);
1013
tok = (GString *)daToks->get(tmPos + 5);
1015
tok->appendf("{0:.2f}", y);
1018
// write the DA string
1020
for (i = 0; i < daToks->getLength(); ++i) {
1021
appearBuf->append((GString *)daToks->get(i))->append(' ');
1025
// write the font matrix (if not part of the DA string)
1027
appearBuf->appendf("1 0 0 1 {0:.2f} {1:.2f} Tm\n", x, y);
1030
// write the text string
1031
appearBuf->append('(');
1032
for (i = 0; i < text->getLength(); ++i) {
1033
c = text->getChar(i) & 0xff;
1034
if (c == '(' || c == ')' || c == '\\') {
1035
appearBuf->append('\\');
1036
appearBuf->append(c);
1037
} else if (c < 0x20 || c >= 0x80) {
1038
appearBuf->appendf("\\{0:03o}", c);
1040
appearBuf->append(c);
1043
appearBuf->append(") Tj\n");
1048
appearBuf->append("ET\n");
1049
appearBuf->append("Q\n");
1051
appearBuf->append("EMC\n");
1055
deleteGList(daToks, GString);
1059
// Draw the variable text or caption for a field.
1060
void Annot::drawListBox(GString **text, GBool *selection,
1061
int nOptions, int topIdx,
1062
GString *da, GfxFontDict *fontDict, GBool quadding) {
1066
double fontSize, fontSize2, border, x, y, w, wMax;
1067
int tfPos, tmPos, i, j, c;
1069
//~ if there is no MK entry, this should use the existing content stream,
1070
//~ and only replace the marked content portion of it
1071
//~ (this is only relevant for Tx fields)
1073
// parse the default appearance string
1076
daToks = new GList();
1078
while (i < da->getLength()) {
1079
while (i < da->getLength() && Lexer::isSpace(da->getChar(i))) {
1082
if (i < da->getLength()) {
1084
j < da->getLength() && !Lexer::isSpace(da->getChar(j));
1086
daToks->append(new GString(da, i, j - i));
1090
for (i = 2; i < daToks->getLength(); ++i) {
1091
if (i >= 2 && !((GString *)daToks->get(i))->cmp("Tf")) {
1093
} else if (i >= 6 && !((GString *)daToks->get(i))->cmp("Tm")) {
1101
// get the font and font size
1105
tok = (GString *)daToks->get(tfPos);
1106
if (tok->getLength() >= 1 && tok->getChar(0) == '/') {
1107
if (!fontDict || !(font = fontDict->lookup(tok->getCString() + 1))) {
1108
error(-1, "Unknown font in field's DA string");
1111
error(-1, "Invalid font name in 'Tf' operator in field's DA string");
1113
tok = (GString *)daToks->get(tfPos + 1);
1114
fontSize = atof(tok->getCString());
1116
error(-1, "Missing 'Tf' operator in field's DA string");
1119
// get the border width
1120
border = borderStyle->getWidth();
1122
// compute font autosize
1123
if (fontSize == 0) {
1125
for (i = 0; i < nOptions; ++i) {
1126
if (font && !font->isCIDFont()) {
1128
for (j = 0; j < text[i]->getLength(); ++j) {
1129
w += ((Gfx8BitFont *)font)->getWidth(text[i]->getChar(j));
1132
// otherwise, make a crude estimate
1133
w = text[i]->getLength() * 0.5;
1139
fontSize = yMax - yMin - 2 * border;
1140
fontSize2 = (xMax - xMin - 4 - 2 * border) / wMax;
1141
if (fontSize2 < fontSize) {
1142
fontSize = fontSize2;
1144
fontSize = floor(fontSize);
1146
tok = (GString *)daToks->get(tfPos + 1);
1148
tok->appendf("{0:.2f}", fontSize);
1153
y = yMax - yMin - 1.1 * fontSize;
1154
for (i = topIdx; i < nOptions; ++i) {
1157
appearBuf->append("q\n");
1159
// draw the background if selected
1161
appearBuf->append("0 g f\n");
1162
appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} re f\n",
1165
xMax - xMin - 2 * border,
1170
appearBuf->append("BT\n");
1172
// compute string width
1173
if (font && !font->isCIDFont()) {
1175
for (j = 0; j < text[i]->getLength(); ++j) {
1176
w += ((Gfx8BitFont *)font)->getWidth(text[i]->getChar(j));
1179
// otherwise, make a crude estimate
1180
w = text[i]->getLength() * 0.5;
1183
// compute text start position
1190
case fieldQuadCenter:
1191
x = (xMax - xMin - w) / 2;
1193
case fieldQuadRight:
1194
x = xMax - xMin - border - 2 - w;
1198
// set the font matrix
1200
tok = (GString *)daToks->get(tmPos + 4);
1202
tok->appendf("{0:.2f}", x);
1203
tok = (GString *)daToks->get(tmPos + 5);
1205
tok->appendf("{0:.2f}", y);
1208
// write the DA string
1210
for (j = 0; j < daToks->getLength(); ++j) {
1211
appearBuf->append((GString *)daToks->get(j))->append(' ');
1215
// write the font matrix (if not part of the DA string)
1217
appearBuf->appendf("1 0 0 1 {0:.2f} {1:.2f} Tm\n", x, y);
1220
// change the text color if selected
1222
appearBuf->append("1 g\n");
1225
// write the text string
1226
appearBuf->append('(');
1227
for (j = 0; j < text[i]->getLength(); ++j) {
1228
c = text[i]->getChar(j) & 0xff;
1229
if (c == '(' || c == ')' || c == '\\') {
1230
appearBuf->append('\\');
1231
appearBuf->append(c);
1232
} else if (c < 0x20 || c >= 0x80) {
1233
appearBuf->appendf("\\{0:03o}", c);
1235
appearBuf->append(c);
1238
appearBuf->append(") Tj\n");
1241
appearBuf->append("ET\n");
1242
appearBuf->append("Q\n");
1245
y -= 1.1 * fontSize;
1249
deleteGList(daToks, GString);
1253
// Figure out how much text will fit on the next line. Returns:
1254
// *end = one past the last character to be included
1255
// *width = width of the characters start .. end-1
1256
// *next = index of first character on the following line
1257
void Annot::getNextLine(GString *text, int start,
1258
GfxFont *font, double fontSize, double wMax,
1259
int *end, double *width, int *next) {
1263
// figure out how much text will fit on the line
1264
//~ what does Adobe do with tabs?
1266
for (j = start; j < text->getLength() && w <= wMax; ++j) {
1267
c = text->getChar(j) & 0xff;
1268
if (c == 0x0a || c == 0x0d) {
1271
if (font && !font->isCIDFont()) {
1272
dw = ((Gfx8BitFont *)font)->getWidth(c) * fontSize;
1274
// otherwise, make a crude estimate
1275
dw = 0.5 * fontSize;
1280
for (k = j; k > start && text->getChar(k-1) != ' '; --k) ;
1281
for (; k > start && text->getChar(k-1) == ' '; --k) ;
1286
// handle the pathological case where the first character is
1287
// too wide to fit on the line all by itself
1293
// compute the width
1295
for (k = start; k < j; ++k) {
1296
if (font && !font->isCIDFont()) {
1297
dw = ((Gfx8BitFont *)font)->getWidth(text->getChar(k)) * fontSize;
1299
// otherwise, make a crude estimate
1300
dw = 0.5 * fontSize;
1307
while (j < text->getLength() && text->getChar(j) == ' ') {
1310
if (j < text->getLength() && text->getChar(j) == 0x0d) {
1313
if (j < text->getLength() && text->getChar(j) == 0x0a) {
1319
// Draw an (approximate) circle of radius <r> centered at (<cx>, <cy>).
1320
// If <fill> is true, the circle is filled; otherwise it is stroked.
1321
void Annot::drawCircle(double cx, double cy, double r, GBool fill) {
1322
appearBuf->appendf("{0:.2f} {1:.2f} m\n",
1324
appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
1325
cx + r, cy + bezierCircle * r,
1326
cx + bezierCircle * r, cy + r,
1328
appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
1329
cx - bezierCircle * r, cy + r,
1330
cx - r, cy + bezierCircle * r,
1332
appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
1333
cx - r, cy - bezierCircle * r,
1334
cx - bezierCircle * r, cy - r,
1336
appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
1337
cx + bezierCircle * r, cy - r,
1338
cx + r, cy - bezierCircle * r,
1340
appearBuf->append(fill ? "f\n" : "s\n");
1343
// Draw the top-left half of an (approximate) circle of radius <r>
1344
// centered at (<cx>, <cy>).
1345
void Annot::drawCircleTopLeft(double cx, double cy, double r) {
1349
appearBuf->appendf("{0:.2f} {1:.2f} m\n",
1351
appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
1352
cx + (1 - bezierCircle) * r2,
1353
cy + (1 + bezierCircle) * r2,
1354
cx - (1 - bezierCircle) * r2,
1355
cy + (1 + bezierCircle) * r2,
1358
appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
1359
cx - (1 + bezierCircle) * r2,
1360
cy + (1 - bezierCircle) * r2,
1361
cx - (1 + bezierCircle) * r2,
1362
cy - (1 - bezierCircle) * r2,
1365
appearBuf->append("S\n");
1368
// Draw the bottom-right half of an (approximate) circle of radius <r>
1369
// centered at (<cx>, <cy>).
1370
void Annot::drawCircleBottomRight(double cx, double cy, double r) {
1374
appearBuf->appendf("{0:.2f} {1:.2f} m\n",
1376
appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
1377
cx - (1 - bezierCircle) * r2,
1378
cy - (1 + bezierCircle) * r2,
1379
cx + (1 - bezierCircle) * r2,
1380
cy - (1 + bezierCircle) * r2,
1383
appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
1384
cx + (1 + bezierCircle) * r2,
1385
cy - (1 - bezierCircle) * r2,
1386
cx + (1 + bezierCircle) * r2,
1387
cy + (1 - bezierCircle) * r2,
1390
appearBuf->append("S\n");
1393
// Look up an inheritable field dictionary entry.
1394
Object *Annot::fieldLookup(Dict *field, char *key, Object *obj) {
1399
if (!dict->lookup(key, obj)->isNull()) {
1403
if (dict->lookup("Parent", &parent)->isDict()) {
1404
fieldLookup(parent.getDict(), key, obj);
1412
void Annot::draw(Gfx *gfx, GBool printing) {
1417
if ((flags & annotFlagHidden) ||
1418
(printing && !(flags & annotFlagPrint)) ||
1419
(!printing && (flags & annotFlagNoView))) {
1423
// draw the appearance stream
1424
isLink = type && !type->cmp("Link");
1425
appearance.fetch(xref, &obj);
1426
gfx->drawAnnot(&obj, isLink ? borderStyle : (AnnotBorderStyle *)NULL,
1427
xMin, yMin, xMax, yMax);
1431
//------------------------------------------------------------------------
1433
//------------------------------------------------------------------------
1435
Annots::Annots(XRef *xref, Catalog *catalog, Object *annotsObj) {
1447
acroForm = catalog->getAcroForm()->isDict() ?
1448
catalog->getAcroForm()->getDict() : NULL;
1449
if (annotsObj->isArray()) {
1450
for (i = 0; i < annotsObj->arrayGetLength(); ++i) {
1451
if (annotsObj->arrayGetNF(i, &obj1)->isRef()) {
1452
ref = obj1.getRef();
1454
annotsObj->arrayGet(i, &obj1);
1456
ref.num = ref.gen = -1;
1458
if (obj1.isDict()) {
1459
annot = new Annot(xref, acroForm, obj1.getDict(), &ref);
1460
if (annot->isOk()) {
1461
if (nAnnots >= size) {
1463
annots = (Annot **)greallocn(annots, size, sizeof(Annot *));
1465
annots[nAnnots++] = annot;
1478
for (i = 0; i < nAnnots; ++i) {
1484
void Annots::generateAppearances(Dict *acroForm) {
1489
if (acroForm->lookup("Fields", &obj1)->isArray()) {
1490
for (i = 0; i < obj1.arrayGetLength(); ++i) {
1491
if (obj1.arrayGetNF(i, &obj2)->isRef()) {
1492
ref = obj2.getRef();
1494
obj1.arrayGet(i, &obj2);
1496
ref.num = ref.gen = -1;
1498
if (obj2.isDict()) {
1499
scanFieldAppearances(obj2.getDict(), &ref, NULL, acroForm);
1507
void Annots::scanFieldAppearances(Dict *node, Ref *ref, Dict *parent,
1514
// non-terminal node: scan the children
1515
if (node->lookup("Kids", &obj1)->isArray()) {
1516
for (i = 0; i < obj1.arrayGetLength(); ++i) {
1517
if (obj1.arrayGetNF(i, &obj2)->isRef()) {
1518
ref2 = obj2.getRef();
1520
obj1.arrayGet(i, &obj2);
1522
ref2.num = ref2.gen = -1;
1524
if (obj2.isDict()) {
1525
scanFieldAppearances(obj2.getDict(), &ref2, node, acroForm);
1534
// terminal node: this is either a combined annot/field dict, or an
1535
// annot dict whose parent is a field
1536
if ((annot = findAnnot(ref))) {
1537
node->lookupNF("Parent", &obj1);
1538
if (!parent || !obj1.isNull()) {
1539
annot->generateFieldAppearance(node, node, acroForm);
1541
annot->generateFieldAppearance(parent, node, acroForm);
1547
Annot *Annots::findAnnot(Ref *ref) {
1550
for (i = 0; i < nAnnots; ++i) {
1551
if (annots[i]->match(ref)) {