1 /*
2  * $Id: PdfAnnotation.java 3917 2009-04-27 12:52:16Z blowagie $
3  *
4  * Copyright 1999, 2000, 2001, 2002 Bruno Lowagie
5  *
6  * The contents of this file are subject to the Mozilla Public License Version 1.1
7  * (the "License"); you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at http://www.mozilla.org/MPL/
9  *
10  * Software distributed under the License is distributed on an "AS IS" basis,
11  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12  * for the specific language governing rights and limitations under the License.
13  *
14  * The Original Code is 'iText, a free JAVA-PDF library'.
15  *
16  * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
17  * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
18  * All Rights Reserved.
19  * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
20  * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
21  *
22  * Contributor(s): all the names of the contributors are added in the source code
23  * where applicable.
24  *
25  * Alternatively, the contents of this file may be used under the terms of the
26  * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
27  * provisions of LGPL are applicable instead of those above.  If you wish to
28  * allow use of your version of this file only under the terms of the LGPL
29  * License and not to allow others to use your version of this file under
30  * the MPL, indicate your decision by deleting the provisions above and
31  * replace them with the notice and other provisions required by the LGPL.
32  * If you do not delete the provisions above, a recipient may use your version
33  * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
34  *
35  * This library is free software; you can redistribute it and/or modify it
36  * under the terms of the MPL as stated above or under the terms of the GNU
37  * Library General Public License as published by the Free Software Foundation;
38  * either version 2 of the License, or any later version.
39  *
40  * This library is distributed in the hope that it will be useful, but WITHOUT
41  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
42  * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
43  * details.
44  *
45  * If you didn't download this code from the following link, you should check if
46  * you aren't using an obsolete version:
47  * http://www.lowagie.com/iText/
48  */

49
50 package com.lowagie.text.pdf;
51
52 import java.awt.Color;
53 import java.io.IOException;
54 import java.util.HashMap;
55
56 import com.lowagie.text.Rectangle;
57 /**
58  * A <CODE>PdfAnnotation</CODE> is a note that is associated with a page.
59  *
60  * @see        PdfDictionary
61  */

62
63 public class PdfAnnotation extends PdfDictionary {
64     /** highlight attributename */
65     public static final PdfName HIGHLIGHT_NONE = PdfName.N;
66     /** highlight attributename */
67     public static final PdfName HIGHLIGHT_INVERT = PdfName.I;
68     /** highlight attributename */
69     public static final PdfName HIGHLIGHT_OUTLINE = PdfName.O;
70     /** highlight attributename */
71     public static final PdfName HIGHLIGHT_PUSH = PdfName.P;
72     /** highlight attributename */
73     public static final PdfName HIGHLIGHT_TOGGLE = PdfName.T;
74     /** flagvalue */
75     public static final int FLAGS_INVISIBLE = 1;
76     /** flagvalue */
77     public static final int FLAGS_HIDDEN = 2;
78     /** flagvalue */
79     public static final int FLAGS_PRINT = 4;
80     /** flagvalue */
81     public static final int FLAGS_NOZOOM = 8;
82     /** flagvalue */
83     public static final int FLAGS_NOROTATE = 16;
84     /** flagvalue */
85     public static final int FLAGS_NOVIEW = 32;
86     /** flagvalue */
87     public static final int FLAGS_READONLY = 64;
88     /** flagvalue */
89     public static final int FLAGS_LOCKED = 128;
90     /** flagvalue */
91     public static final int FLAGS_TOGGLENOVIEW = 256;
92     /** appearance attributename */
93     public static final PdfName APPEARANCE_NORMAL = PdfName.N;
94     /** appearance attributename */
95     public static final PdfName APPEARANCE_ROLLOVER = PdfName.R;
96     /** appearance attributename */
97     public static final PdfName APPEARANCE_DOWN = PdfName.D;
98     /** attributevalue */
99     public static final PdfName AA_ENTER = PdfName.E;
100     /** attributevalue */
101     public static final PdfName AA_EXIT = PdfName.X;
102     /** attributevalue */
103     public static final PdfName AA_DOWN = PdfName.D;
104     /** attributevalue */
105     public static final PdfName AA_UP = PdfName.U;
106     /** attributevalue */
107     public static final PdfName AA_FOCUS = PdfName.FO;
108     /** attributevalue */
109     public static final PdfName AA_BLUR = PdfName.BL;
110     /** attributevalue */
111     public static final PdfName AA_JS_KEY = PdfName.K;
112     /** attributevalue */
113     public static final PdfName AA_JS_FORMAT = PdfName.F;
114     /** attributevalue */
115     public static final PdfName AA_JS_CHANGE = PdfName.V;
116     /** attributevalue */
117     public static final PdfName AA_JS_OTHER_CHANGE = PdfName.C;
118     /** attributevalue */
119     public static final int MARKUP_HIGHLIGHT = 0;
120     /** attributevalue */
121     public static final int MARKUP_UNDERLINE = 1;
122     /** attributevalue */
123     public static final int MARKUP_STRIKEOUT = 2;
124     /**
125      * attributevalue
126      * @since 2.1.3
127      */

128     public static final int MARKUP_SQUIGGLY = 3;
129
130     protected PdfWriter writer;
131     /**
132      * Reference to this annotation.
133      * @since    2.1.6; was removed in 2.1.5, but restored in 2.1.6
134      */

135     protected PdfIndirectReference reference;
136     protected HashMap templates;
137     protected boolean form = false;
138     protected boolean annotation = true;
139
140     /** Holds value of property used. */
141     protected boolean used = false;
142
143     /** Holds value of property placeInPage. */
144     private int placeInPage = -1;
145
146     // constructors
147     public PdfAnnotation(PdfWriter writer, Rectangle rect) {
148         this.writer = writer;
149         if (rect != null)
150             put(PdfName.RECT, new PdfRectangle(rect));
151     }
152
153 /**
154  * Constructs a new <CODE>PdfAnnotation</CODE> of subtype text.
155  * @param writer
156  * @param llx
157  * @param lly
158  * @param urx
159  * @param ury
160  * @param title
161  * @param content
162  */

163
164     public PdfAnnotation(PdfWriter writer, float llx, float lly, float urx, float ury, PdfString title, PdfString content) {
165         this.writer = writer;
166         put(PdfName.SUBTYPE, PdfName.TEXT);
167         put(PdfName.T, title);
168         put(PdfName.RECT, new PdfRectangle(llx, lly, urx, ury));
169         put(PdfName.CONTENTS, content);
170     }
171
172 /**
173  * Constructs a new <CODE>PdfAnnotation</CODE> of subtype link (Action).
174  * @param writer
175  * @param llx
176  * @param lly
177  * @param urx
178  * @param ury
179  * @param action
180  */

181
182     public PdfAnnotation(PdfWriter writer, float llx, float lly, float urx, float ury, PdfAction action) {
183         this.writer = writer;
184         put(PdfName.SUBTYPE, PdfName.LINK);
185         put(PdfName.RECT, new PdfRectangle(llx, lly, urx, ury));
186         put(PdfName.A, action);
187         put(PdfName.BORDER, new PdfBorderArray(0, 0, 0));
188         put(PdfName.C, new PdfColor(0x00, 0x00, 0xFF));
189     }
190
191     /**
192      * Creates a screen PdfAnnotation
193      * @param writer
194      * @param rect
195      * @param clipTitle
196      * @param fs
197      * @param mimeType
198      * @param playOnDisplay
199      * @return a screen PdfAnnotation
200      * @throws IOException
201      */

202     public static PdfAnnotation createScreen(PdfWriter writer, Rectangle rect, String clipTitle, PdfFileSpecification fs,
203                                              String mimeType, boolean playOnDisplay) throws IOException {
204         PdfAnnotation ann = new PdfAnnotation(writer, rect);
205         ann.put(PdfName.SUBTYPE, PdfName.SCREEN);
206         ann.put (PdfName.F, new PdfNumber(FLAGS_PRINT));
207         ann.put(PdfName.TYPE, PdfName.ANNOT);
208         ann.setPage();
209         PdfIndirectReference ref = ann.getIndirectReference();
210         PdfAction action = PdfAction.rendition(clipTitle,fs,mimeType, ref);
211         PdfIndirectReference actionRef = writer.addToBody(action).getIndirectReference();
212         // for play on display add trigger event
213         if (playOnDisplay)
214         {
215             PdfDictionary aa = new PdfDictionary();
216             aa.put(new PdfName("PV"), actionRef);
217             ann.put(PdfName.AA, aa);
218         }
219         ann.put(PdfName.A, actionRef);
220         return ann;
221     }
222
223     /**
224      * Returns an indirect reference to the annotation
225      * @return the indirect reference
226      */

227     public PdfIndirectReference getIndirectReference() {
228         if (reference == null) {
229             reference = writer.getPdfIndirectReference();
230         }
231         return reference;
232     }
233
234     /**
235      * @param writer
236      * @param rect
237      * @param title
238      * @param contents
239      * @param open
240      * @param icon
241      * @return a PdfAnnotation
242      */

243     public static PdfAnnotation createText(PdfWriter writer, Rectangle rect, String title, String contents, boolean open, String icon) {
244         PdfAnnotation annot = new PdfAnnotation(writer, rect);
245         annot.put(PdfName.SUBTYPE, PdfName.TEXT);
246         if (title != null)
247             annot.put(PdfName.T, new PdfString(title, PdfObject.TEXT_UNICODE));
248         if (contents != null)
249             annot.put(PdfName.CONTENTS, new PdfString(contents, PdfObject.TEXT_UNICODE));
250         if (open)
251             annot.put(PdfName.OPEN, PdfBoolean.PDFTRUE);
252         if (icon != null) {
253             annot.put(PdfName.NAME, new PdfName(icon));
254         }
255         return annot;
256     }
257
258     /**
259      * Creates a link.
260      * @param writer
261      * @param rect
262      * @param highlight
263      * @return A PdfAnnotation
264      */

265     protected static PdfAnnotation createLink(PdfWriter writer, Rectangle rect, PdfName highlight) {
266         PdfAnnotation annot = new PdfAnnotation(writer, rect);
267         annot.put(PdfName.SUBTYPE, PdfName.LINK);
268         if (!highlight.equals(HIGHLIGHT_INVERT))
269             annot.put(PdfName.H, highlight);
270         return annot;
271     }
272
273     /**
274      * Creates an Annotation with an Action.
275      * @param writer
276      * @param rect
277      * @param highlight
278      * @param action
279      * @return A PdfAnnotation
280      */

281     public static PdfAnnotation createLink(PdfWriter writer, Rectangle rect, PdfName highlight, PdfAction action) {
282         PdfAnnotation annot = createLink(writer, rect, highlight);
283         annot.putEx(PdfName.A, action);
284         return annot;
285     }
286
287     /**
288      * Creates an Annotation with an local destination.
289      * @param writer
290      * @param rect
291      * @param highlight
292      * @param namedDestination
293      * @return A PdfAnnotation
294      */

295     public static PdfAnnotation createLink(PdfWriter writer, Rectangle rect, PdfName highlight, String namedDestination) {
296         PdfAnnotation annot = createLink(writer, rect, highlight);
297         annot.put(PdfName.DEST, new PdfString(namedDestination));
298         return annot;
299     }
300
301     /**
302      * Creates an Annotation with a PdfDestination.
303      * @param writer
304      * @param rect
305      * @param highlight
306      * @param page
307      * @param dest
308      * @return A PdfAnnotation
309      */

310     public static PdfAnnotation createLink(PdfWriter writer, Rectangle rect, PdfName highlight, int page, PdfDestination dest) {
311         PdfAnnotation annot = createLink(writer, rect, highlight);
312         PdfIndirectReference ref = writer.getPageReference(page);
313         dest.addPage(ref);
314         annot.put(PdfName.DEST, dest);
315         return annot;
316     }
317
318     /**
319      * Add some free text to the document.
320      * @param writer
321      * @param rect
322      * @param contents
323      * @param defaultAppearance
324      * @return A PdfAnnotation
325      */

326     public static PdfAnnotation createFreeText(PdfWriter writer, Rectangle rect, String contents, PdfContentByte defaultAppearance) {
327         PdfAnnotation annot = new PdfAnnotation(writer, rect);
328         annot.put(PdfName.SUBTYPE, PdfName.FREETEXT);
329         annot.put(PdfName.CONTENTS, new PdfString(contents, PdfObject.TEXT_UNICODE));
330         annot.setDefaultAppearanceString(defaultAppearance);
331         return annot;
332     }
333
334     /**
335      * Adds a line to the document. Move over the line and a tooltip is shown.
336      * @param writer
337      * @param rect
338      * @param contents
339      * @param x1
340      * @param y1
341      * @param x2
342      * @param y2
343      * @return A PdfAnnotation
344      */

345     public static PdfAnnotation createLine(PdfWriter writer, Rectangle rect, String contents, float x1, float y1, float x2, float y2) {
346         PdfAnnotation annot = new PdfAnnotation(writer, rect);
347         annot.put(PdfName.SUBTYPE, PdfName.LINE);
348         annot.put(PdfName.CONTENTS, new PdfString(contents, PdfObject.TEXT_UNICODE));
349         PdfArray array = new PdfArray(new PdfNumber(x1));
350         array.add(new PdfNumber(y1));
351         array.add(new PdfNumber(x2));
352         array.add(new PdfNumber(y2));
353         annot.put(PdfName.L, array);
354         return annot;
355     }
356
357     /**
358      * Adds a circle or a square that shows a tooltip when you pass over it.
359      * @param writer
360      * @param rect
361      * @param contents The tooltip
362      * @param square true if you want a square, false if you want a circle
363      * @return A PdfAnnotation
364      */

365     public static PdfAnnotation createSquareCircle(PdfWriter writer, Rectangle rect, String contents, boolean square) {
366         PdfAnnotation annot = new PdfAnnotation(writer, rect);
367         if (square)
368             annot.put(PdfName.SUBTYPE, PdfName.SQUARE);
369         else
370             annot.put(PdfName.SUBTYPE, PdfName.CIRCLE);
371         annot.put(PdfName.CONTENTS, new PdfString(contents, PdfObject.TEXT_UNICODE));
372         return annot;
373     }
374
375     public static PdfAnnotation createMarkup(PdfWriter writer, Rectangle rect, String contents, int type, float quadPoints[]) {
376         PdfAnnotation annot = new PdfAnnotation(writer, rect);
377         PdfName name = PdfName.HIGHLIGHT;
378         switch (type) {
379             case MARKUP_UNDERLINE:
380                 name = PdfName.UNDERLINE;
381                 break;
382             case MARKUP_STRIKEOUT:
383                 name = PdfName.STRIKEOUT;
384                 break;
385             case MARKUP_SQUIGGLY:
386                 name = PdfName.SQUIGGLY;
387                 break;
388         }
389         annot.put(PdfName.SUBTYPE, name);
390         annot.put(PdfName.CONTENTS, new PdfString(contents, PdfObject.TEXT_UNICODE));
391         PdfArray array = new PdfArray();
392         for (int k = 0; k < quadPoints.length; ++k)
393             array.add(new PdfNumber(quadPoints[k]));
394         annot.put(PdfName.QUADPOINTS, array);
395         return annot;
396     }
397
398     /**
399      * Adds a Stamp to your document. Move over the stamp and a tooltip is shown
400      * @param writer
401      * @param rect
402      * @param contents
403      * @param name
404      * @return A PdfAnnotation
405      */

406     public static PdfAnnotation createStamp(PdfWriter writer, Rectangle rect, String contents, String name) {
407         PdfAnnotation annot = new PdfAnnotation(writer, rect);
408         annot.put(PdfName.SUBTYPE, PdfName.STAMP);
409         annot.put(PdfName.CONTENTS, new PdfString(contents, PdfObject.TEXT_UNICODE));
410         annot.put(PdfName.NAME, new PdfName(name));
411         return annot;
412     }
413
414     public static PdfAnnotation createInk(PdfWriter writer, Rectangle rect, String contents, float inkList[][]) {
415         PdfAnnotation annot = new PdfAnnotation(writer, rect);
416         annot.put(PdfName.SUBTYPE, PdfName.INK);
417         annot.put(PdfName.CONTENTS, new PdfString(contents, PdfObject.TEXT_UNICODE));
418         PdfArray outer = new PdfArray();
419         for (int k = 0; k < inkList.length; ++k) {
420             PdfArray inner = new PdfArray();
421             float deep[] = inkList[k];
422             for (int j = 0; j < deep.length; ++j)
423                 inner.add(new PdfNumber(deep[j]));
424             outer.add(inner);
425         }
426         annot.put(PdfName.INKLIST, outer);
427         return annot;
428     }
429
430     /** Creates a file attachment annotation.
431      * @param writer the <CODE>PdfWriter</CODE>
432      * @param rect the dimensions in the page of the annotation
433      * @param contents the file description
434      * @param fileStore an array with the file. If it's <CODE>null</CODE>
435      * the file will be read from the disk
436      * @param file the path to the file. It will only be used if
437      * <CODE>fileStore</CODE> is not <CODE>null</CODE>
438      * @param fileDisplay the actual file name stored in the pdf
439      * @throws IOException on error
440      * @return the annotation
441      */

442     public static PdfAnnotation createFileAttachment(PdfWriter writer, Rectangle rect, String contents, byte fileStore[], String file, String fileDisplay) throws IOException {
443         return createFileAttachment(writer, rect, contents, PdfFileSpecification.fileEmbedded(writer, file, fileDisplay, fileStore));
444     }
445
446     /** Creates a file attachment annotation
447      * @param writer
448      * @param rect
449      * @param contents
450      * @param fs
451      * @return the annotation
452      * @throws IOException
453      */

454     public static PdfAnnotation createFileAttachment(PdfWriter writer, Rectangle rect, String contents, PdfFileSpecification fs) throws IOException {
455         PdfAnnotation annot = new PdfAnnotation(writer, rect);
456         annot.put(PdfName.SUBTYPE, PdfName.FILEATTACHMENT);
457         if (contents != null)
458             annot.put(PdfName.CONTENTS, new PdfString(contents, PdfObject.TEXT_UNICODE));
459         annot.put(PdfName.FS, fs.getReference());
460         return annot;
461     }
462
463     /**
464      * Adds a popup to your document.
465      * @param writer
466      * @param rect
467      * @param contents
468      * @param open
469      * @return A PdfAnnotation
470      */

471     public static PdfAnnotation createPopup(PdfWriter writer, Rectangle rect, String contents, boolean open) {
472         PdfAnnotation annot = new PdfAnnotation(writer, rect);
473         annot.put(PdfName.SUBTYPE, PdfName.POPUP);
474         if (contents != null)
475             annot.put(PdfName.CONTENTS, new PdfString(contents, PdfObject.TEXT_UNICODE));
476         if (open)
477             annot.put(PdfName.OPEN, PdfBoolean.PDFTRUE);
478         return annot;
479     }
480
481     public void setDefaultAppearanceString(PdfContentByte cb) {
482         byte b[] = cb.getInternalBuffer().toByteArray();
483         int len = b.length;
484         for (int k = 0; k < len; ++k) {
485             if (b[k] == '\n')
486                 b[k] = 32;
487         }
488         put(PdfName.DA, new PdfString(b));
489     }
490
491     public void setFlags(int flags) {
492         if (flags == 0)
493             remove(PdfName.F);
494         else
495             put(PdfName.F, new PdfNumber(flags));
496     }
497
498     public void setBorder(PdfBorderArray border) {
499         put(PdfName.BORDER, border);
500     }
501
502     public void setBorderStyle(PdfBorderDictionary border) {
503         put(PdfName.BS, border);
504     }
505
506     /**
507      * Sets the annotation's highlighting mode. The values can be
508      * <CODE>HIGHLIGHT_NONE</CODE>, <CODE>HIGHLIGHT_INVERT</CODE>,
509      * <CODE>HIGHLIGHT_OUTLINE</CODE> and <CODE>HIGHLIGHT_PUSH</CODE>;
510      * @param highlight the annotation's highlighting mode
511      */

512     public void setHighlighting(PdfName highlight) {
513         if (highlight.equals(HIGHLIGHT_INVERT))
514             remove(PdfName.H);
515         else
516             put(PdfName.H, highlight);
517     }
518
519     public void setAppearance(PdfName ap, PdfTemplate template) {
520         PdfDictionary dic = (PdfDictionary)get(PdfName.AP);
521         if (dic == null)
522             dic = new PdfDictionary();
523         dic.put(ap, template.getIndirectReference());
524         put(PdfName.AP, dic);
525         if (!form)
526             return;
527         if (templates == null)
528             templates = new HashMap();
529         templates.put(template, null);
530     }
531
532     public void setAppearance(PdfName ap, String state, PdfTemplate template) {
533         PdfDictionary dicAp = (PdfDictionary)get(PdfName.AP);
534         if (dicAp == null)
535             dicAp = new PdfDictionary();
536
537         PdfDictionary dic;
538         PdfObject obj = dicAp.get(ap);
539         if (obj != null && obj.isDictionary())
540             dic = (PdfDictionary)obj;
541         else
542             dic = new PdfDictionary();
543         dic.put(new PdfName(state), template.getIndirectReference());
544         dicAp.put(ap, dic);
545         put(PdfName.AP, dicAp);
546         if (!form)
547             return;
548         if (templates == null)
549             templates = new HashMap();
550         templates.put(template, null);
551     }
552
553     public void setAppearanceState(String state) {
554         if (state == null) {
555             remove(PdfName.AS);
556             return;
557         }
558         put(PdfName.AS, new PdfName(state));
559     }
560
561     public void setColor(Color color) {
562         put(PdfName.C, new PdfColor(color));
563     }
564
565     public void setTitle(String title) {
566         if (title == null) {
567             remove(PdfName.T);
568             return;
569         }
570         put(PdfName.T, new PdfString(title, PdfObject.TEXT_UNICODE));
571     }
572
573     public void setPopup(PdfAnnotation popup) {
574         put(PdfName.POPUP, popup.getIndirectReference());
575         popup.put(PdfName.PARENT, getIndirectReference());
576     }
577
578     public void setAction(PdfAction action) {
579         put(PdfName.A, action);
580     }
581
582     public void setAdditionalActions(PdfName key, PdfAction action) {
583         PdfDictionary dic;
584         PdfObject obj = get(PdfName.AA);
585         if (obj != null && obj.isDictionary())
586             dic = (PdfDictionary)obj;
587         else
588             dic = new PdfDictionary();
589         dic.put(key, action);
590         put(PdfName.AA, dic);
591     }
592
593     /** Getter for property used.
594      * @return Value of property used.
595      */

596     public boolean isUsed() {
597         return used;
598     }
599
600     /** Setter for property used.
601      */

602     public void setUsed() {
603         used = true;
604     }
605
606     public HashMap getTemplates() {
607         return templates;
608     }
609
610     /** Getter for property form.
611      * @return Value of property form.
612      */

613     public boolean isForm() {
614         return form;
615     }
616
617     /** Getter for property annotation.
618      * @return Value of property annotation.
619      */

620     public boolean isAnnotation() {
621         return annotation;
622     }
623
624     public void setPage(int page) {
625         put(PdfName.P, writer.getPageReference(page));
626     }
627
628     public void setPage() {
629         put(PdfName.P, writer.getCurrentPage());
630     }
631
632     /** Getter for property placeInPage.
633      * @return Value of property placeInPage.
634      */

635     public int getPlaceInPage() {
636         return placeInPage;
637     }
638
639     /** Places the annotation in a specified page that must be greater
640      * or equal to the current one. With <code>PdfStamper</code> the page
641      * can be any. The first page is 1.
642      * @param placeInPage New value of property placeInPage.
643      */

644     public void setPlaceInPage(int placeInPage) {
645         this.placeInPage = placeInPage;
646     }
647
648     public void setRotate(int v) {
649         put(PdfName.ROTATE, new PdfNumber(v));
650     }
651
652     PdfDictionary getMK() {
653         PdfDictionary mk = (PdfDictionary)get(PdfName.MK);
654         if (mk == null) {
655             mk = new PdfDictionary();
656             put(PdfName.MK, mk);
657         }
658         return mk;
659     }
660
661     public void setMKRotation(int rotation) {
662         getMK().put(PdfName.R, new PdfNumber(rotation));
663     }
664
665     public static PdfArray getMKColor(Color color) {
666         PdfArray array = new PdfArray();
667         int type = ExtendedColor.getType(color);
668         switch (type) {
669             case ExtendedColor.TYPE_GRAY: {
670                 array.add(new PdfNumber(((GrayColor)color).getGray()));
671                 break;
672             }
673             case ExtendedColor.TYPE_CMYK: {
674                 CMYKColor cmyk = (CMYKColor)color;
675                 array.add(new PdfNumber(cmyk.getCyan()));
676                 array.add(new PdfNumber(cmyk.getMagenta()));
677                 array.add(new PdfNumber(cmyk.getYellow()));
678                 array.add(new PdfNumber(cmyk.getBlack()));
679                 break;
680             }
681             case ExtendedColor.TYPE_SEPARATION:
682             case ExtendedColor.TYPE_PATTERN:
683             case ExtendedColor.TYPE_SHADING:
684                 throw new RuntimeException("Separations, patterns and shadings are not allowed in MK dictionary.");
685             default:
686                 array.add(new PdfNumber(color.getRed() / 255f));
687                 array.add(new PdfNumber(color.getGreen() / 255f));
688                 array.add(new PdfNumber(color.getBlue() / 255f));
689         }
690         return array;
691     }
692
693     public void setMKBorderColor(Color color) {
694         if (color == null)
695             getMK().remove(PdfName.BC);
696         else
697             getMK().put(PdfName.BC, getMKColor(color));
698     }
699
700     public void setMKBackgroundColor(Color color) {
701         if (color == null)
702             getMK().remove(PdfName.BG);
703         else
704             getMK().put(PdfName.BG, getMKColor(color));
705     }
706
707     public void setMKNormalCaption(String caption) {
708         getMK().put(PdfName.CA, new PdfString(caption, PdfObject.TEXT_UNICODE));
709     }
710
711     public void setMKRolloverCaption(String caption) {
712         getMK().put(PdfName.RC, new PdfString(caption, PdfObject.TEXT_UNICODE));
713     }
714
715     public void setMKAlternateCaption(String caption) {
716         getMK().put(PdfName.AC, new PdfString(caption, PdfObject.TEXT_UNICODE));
717     }
718
719     public void setMKNormalIcon(PdfTemplate template) {
720         getMK().put(PdfName.I, template.getIndirectReference());
721     }
722
723     public void setMKRolloverIcon(PdfTemplate template) {
724         getMK().put(PdfName.RI, template.getIndirectReference());
725     }
726
727     public void setMKAlternateIcon(PdfTemplate template) {
728         getMK().put(PdfName.IX, template.getIndirectReference());
729     }
730
731     public void setMKIconFit(PdfName scale, PdfName scalingType, float leftoverLeft, float leftoverBottom, boolean fitInBounds) {
732         PdfDictionary dic = new PdfDictionary();
733         if (!scale.equals(PdfName.A))
734             dic.put(PdfName.SW, scale);
735         if (!scalingType.equals(PdfName.P))
736             dic.put(PdfName.S, scalingType);
737         if (leftoverLeft != 0.5f || leftoverBottom != 0.5f) {
738             PdfArray array = new PdfArray(new PdfNumber(leftoverLeft));
739             array.add(new PdfNumber(leftoverBottom));
740             dic.put(PdfName.A, array);
741         }
742         if (fitInBounds)
743             dic.put(PdfName.FB, PdfBoolean.PDFTRUE);
744         getMK().put(PdfName.IF, dic);
745     }
746
747     public void setMKTextPosition(int tp) {
748         getMK().put(PdfName.TP, new PdfNumber(tp));
749     }
750
751     /**
752      * Sets the layer this annotation belongs to.
753      * @param layer the layer this annotation belongs to
754      */

755     public void setLayer(PdfOCG layer) {
756         put(PdfName.OC, layer.getRef());
757     }
758
759     /**
760      * Sets the name of the annotation.
761      * With this name the annotation can be identified among
762      * all the annotations on a page (it has to be unique).
763      */

764     public void setName(String name) {
765         put(PdfName.NM, new PdfString(name));
766     }
767
768     /**
769      * This class processes links from imported pages so that they may be active. The following example code reads a group
770      * of files and places them all on the output PDF, four pages in a single page, keeping the links active.
771      * <pre>
772      * String[] files = new String[] {&quot;input1.pdf&quot;, &quot;input2.pdf&quot;};
773      * String outputFile = &quot;output.pdf&quot;;
774      * int firstPage=1;
775      * Document document = new Document();
776      * PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(outputFile));
777      * document.setPageSize(PageSize.A4);
778      * float W = PageSize.A4.getWidth() / 2;
779      * float H = PageSize.A4.getHeight() / 2;
780      * document.open();
781      * PdfContentByte cb = writer.getDirectContent();
782      * for (int i = 0; i &lt; files.length; i++) {
783      *    PdfReader currentReader = new PdfReader(files[i]);
784      *    currentReader.consolidateNamedDestinations();
785      *    for (int page = 1; page &lt;= currentReader.getNumberOfPages(); page++) {
786      *        PdfImportedPage importedPage = writer.getImportedPage(currentReader, page);
787      *        float a = 0.5f;
788      *        float e = (page % 2 == 0) ? W : 0;
789      *        float f = (page % 4 == 1 || page % 4 == 2) ? H : 0;
790      *        ArrayList links = currentReader.getLinks(page);
791      *        cb.addTemplate(importedPage, a, 0, 0, a, e, f);
792      *        for (int j = 0; j &lt; links.size(); j++) {
793      *            PdfAnnotation.PdfImportedLink link = (PdfAnnotation.PdfImportedLink)links.get(j);
794      *            if (link.isInternal()) {
795      *                int dPage = link.getDestinationPage();
796      *                int newDestPage = (dPage-1)/4 + firstPage;
797      *                float ee = (dPage % 2 == 0) ? W : 0;
798      *                float ff = (dPage % 4 == 1 || dPage % 4 == 2) ? H : 0;
799      *                link.setDestinationPage(newDestPage);
800      *                link.transformDestination(a, 0, 0, a, ee, ff);
801      *            }
802      *            link.transformRect(a, 0, 0, a, e, f);
803      *            writer.addAnnotation(link.createAnnotation(writer));
804      *        }
805      *        if (page % 4 == 0)
806      *        document.newPage();
807      *    }
808      *    if (i &lt; files.length - 1)
809      *    document.newPage();
810      *    firstPage += (currentReader.getNumberOfPages()+3)/4;
811      * }
812      * document.close();
813      * </pre>
814      */

815     public static class PdfImportedLink {
816         float llx, lly, urx, ury;
817         HashMap parameters = new HashMap();
818         PdfArray destination = null;
819         int newPage=0;
820
821         PdfImportedLink(PdfDictionary annotation) {
822             parameters.putAll(annotation.hashMap);
823             try {
824                 destination = (PdfArray) parameters.remove(PdfName.DEST);
825             } catch (ClassCastException ex) {
826                 throw new IllegalArgumentException("You have to consolidate the named destinations of your reader.");
827             }
828             if (destination != null) {
829                 destination = new PdfArray(destination);
830             }
831             PdfArray rc = (PdfArray) parameters.remove(PdfName.RECT);
832             llx = rc.getAsNumber(0).floatValue();
833             lly = rc.getAsNumber(1).floatValue();
834             urx = rc.getAsNumber(2).floatValue();
835             ury = rc.getAsNumber(3).floatValue();
836         }
837
838         public boolean isInternal() {
839             return destination != null;
840         }
841
842         public int getDestinationPage() {
843             if (!isInternal()) return 0;
844
845             // here destination is something like
846             // [132 0 R, /XYZ, 29.3898, 731.864502, null]
847             PdfIndirectReference ref = destination.getAsIndirectObject(0);
848
849             PRIndirectReference pr = (PRIndirectReference) ref;
850             PdfReader r = pr.getReader();
851             for (int i = 1; i <= r.getNumberOfPages(); i++) {
852                 PRIndirectReference pp = r.getPageOrigRef(i);
853                 if (pp.getGeneration() == pr.getGeneration() && pp.getNumber() == pr.getNumber()) return i;
854             }
855             throw new IllegalArgumentException("Page not found.");
856         }
857
858         public void setDestinationPage(int newPage) {
859             if (!isInternal()) throw new IllegalArgumentException("Cannot change destination of external link");
860             this.newPage=newPage;
861         }
862
863         public void transformDestination(float a, float b, float c, float d, float e, float f) {
864             if (!isInternal()) throw new IllegalArgumentException("Cannot change destination of external link");
865             if (destination.getAsName(1).equals(PdfName.XYZ)) {
866                 float x = destination.getAsNumber(2).floatValue();
867                 float y = destination.getAsNumber(3).floatValue();
868                 float xx = x * a + y * c + e;
869                 float yy = x * b + y * d + f;
870                 destination.set(2, new PdfNumber(xx));
871                 destination.set(3, new PdfNumber(yy));
872             }
873         }
874
875         public void transformRect(float a, float b, float c, float d, float e, float f) {
876             float x = llx * a + lly * c + e;
877             float y = llx * b + lly * d + f;
878             llx = x;
879             lly = y;
880             x = urx * a + ury * c + e;
881             y = urx * b + ury * d + f;
882             urx = x;
883             ury = y;
884         }
885
886         public PdfAnnotation createAnnotation(PdfWriter writer) {
887             PdfAnnotation annotation = new PdfAnnotation(writer, new Rectangle(llx, lly, urx, ury));
888             if (newPage != 0) {
889                 PdfIndirectReference ref = writer.getPageReference(newPage);
890                 destination.set(0, ref);
891             }
892             if (destination != null) annotation.put(PdfName.DEST, destination);
893             annotation.hashMap.putAll(parameters);
894             return annotation;
895         }
896         
897         /**
898          * Returns a String representation of the link.
899          * @return    a String representation of the imported link
900          * @since    2.1.6
901          */

902         public String toString() {
903             StringBuffer buf = new StringBuffer("Imported link: location [");
904             buf.append(llx);
905             buf.append(' ');
906             buf.append(lly);
907             buf.append(' ');
908             buf.append(urx);
909             buf.append(' ');
910             buf.append(ury);
911             buf.append("] destination ");
912             buf.append(destination);
913             buf.append(" parameters ");
914             buf.append(parameters);
915             return buf.toString();
916         }
917     }
918 }
919