001/* 002 * $Id: PdfPageLabels.java 4784 2011-03-15 08:33:00Z blowagie $ 003 * 004 * This file is part of the iText (R) project. 005 * Copyright (c) 1998-2011 1T3XT BVBA 006 * Authors: Bruno Lowagie, Paulo Soares, et al. 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU Affero General Public License version 3 010 * as published by the Free Software Foundation with the addition of the 011 * following permission added to Section 15 as permitted in Section 7(a): 012 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY 1T3XT, 013 * 1T3XT DISCLAIMS THE WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. 014 * 015 * This program is distributed in the hope that it will be useful, but 016 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 017 * or FITNESS FOR A PARTICULAR PURPOSE. 018 * See the GNU Affero General Public License for more details. 019 * You should have received a copy of the GNU Affero General Public License 020 * along with this program; if not, see http://www.gnu.org/licenses or write to 021 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 022 * Boston, MA, 02110-1301 USA, or download the license from the following URL: 023 * http://itextpdf.com/terms-of-use/ 024 * 025 * The interactive user interfaces in modified source and object code versions 026 * of this program must display Appropriate Legal Notices, as required under 027 * Section 5 of the GNU Affero General Public License. 028 * 029 * In accordance with Section 7(b) of the GNU Affero General Public License, 030 * a covered work must retain the producer line in every PDF that is created 031 * or manipulated using iText. 032 * 033 * You can be released from the requirements of the license by purchasing 034 * a commercial license. Buying such a license is mandatory as soon as you 035 * develop commercial activities involving the iText software without 036 * disclosing the source code of your own applications. 037 * These activities include: offering paid services to customers as an ASP, 038 * serving PDFs on the fly in a web application, shipping iText with a closed 039 * source product. 040 * 041 * For more information, please contact iText Software Corp. at this 042 * address: sales@itextpdf.com 043 */ 044package com.itextpdf.text.pdf; 045 046import java.io.IOException; 047import java.util.Arrays; 048import java.util.HashMap; 049 050import com.itextpdf.text.ExceptionConverter; 051import com.itextpdf.text.error_messages.MessageLocalization; 052import com.itextpdf.text.factories.RomanAlphabetFactory; 053import com.itextpdf.text.factories.RomanNumberFactory; 054 055/** Page labels are used to identify each 056 * page visually on the screen or in print. 057 * @author Paulo Soares 058 */ 059public class PdfPageLabels { 060 061 /** Logical pages will have the form 1,2,3,... 062 */ 063 public static final int DECIMAL_ARABIC_NUMERALS = 0; 064 /** Logical pages will have the form I,II,III,IV,... 065 */ 066 public static final int UPPERCASE_ROMAN_NUMERALS = 1; 067 /** Logical pages will have the form i,ii,iii,iv,... 068 */ 069 public static final int LOWERCASE_ROMAN_NUMERALS = 2; 070 /** Logical pages will have the form of uppercase letters 071 * (A to Z for the first 26 pages, AA to ZZ for the next 26, and so on) 072 */ 073 public static final int UPPERCASE_LETTERS = 3; 074 /** Logical pages will have the form of uppercase letters 075 * (a to z for the first 26 pages, aa to zz for the next 26, and so on) 076 */ 077 public static final int LOWERCASE_LETTERS = 4; 078 /** No logical page numbers are generated but fixed text may 079 * still exist 080 */ 081 public static final int EMPTY = 5; 082 /** Dictionary values to set the logical page styles 083 */ 084 static PdfName numberingStyle[] = new PdfName[]{PdfName.D, PdfName.R, 085 new PdfName("r"), PdfName.A, new PdfName("a")}; 086 /** The sequence of logical pages. Will contain at least a value for page 1 087 */ 088 private HashMap<Integer, PdfDictionary> map; 089 090 /** Creates a new PdfPageLabel with a default logical page 1 091 */ 092 public PdfPageLabels() { 093 map = new HashMap<Integer, PdfDictionary>(); 094 addPageLabel(1, DECIMAL_ARABIC_NUMERALS, null, 1); 095 } 096 097 /** Adds or replaces a page label. 098 * @param page the real page to start the numbering. First page is 1 099 * @param numberStyle the numbering style such as LOWERCASE_ROMAN_NUMERALS 100 * @param text the text to prefix the number. Can be <CODE>null</CODE> or empty 101 * @param firstPage the first logical page number 102 */ 103 public void addPageLabel(int page, int numberStyle, String text, int firstPage) { 104 if (page < 1 || firstPage < 1) 105 throw new IllegalArgumentException(MessageLocalization.getComposedMessage("in.a.page.label.the.page.numbers.must.be.greater.or.equal.to.1")); 106 PdfDictionary dic = new PdfDictionary(); 107 if (numberStyle >= 0 && numberStyle < numberingStyle.length) 108 dic.put(PdfName.S, numberingStyle[numberStyle]); 109 if (text != null) 110 dic.put(PdfName.P, new PdfString(text, PdfObject.TEXT_UNICODE)); 111 if (firstPage != 1) 112 dic.put(PdfName.ST, new PdfNumber(firstPage)); 113 map.put(Integer.valueOf(page - 1), dic); 114 } 115 116 /** Adds or replaces a page label. The first logical page has the default 117 * of 1. 118 * @param page the real page to start the numbering. First page is 1 119 * @param numberStyle the numbering style such as LOWERCASE_ROMAN_NUMERALS 120 * @param text the text to prefix the number. Can be <CODE>null</CODE> or empty 121 */ 122 public void addPageLabel(int page, int numberStyle, String text) { 123 addPageLabel(page, numberStyle, text, 1); 124 } 125 126 /** Adds or replaces a page label. There is no text prefix and the first 127 * logical page has the default of 1. 128 * @param page the real page to start the numbering. First page is 1 129 * @param numberStyle the numbering style such as LOWERCASE_ROMAN_NUMERALS 130 */ 131 public void addPageLabel(int page, int numberStyle) { 132 addPageLabel(page, numberStyle, null, 1); 133 } 134 135 /** Adds or replaces a page label. 136 */ 137 public void addPageLabel(PdfPageLabelFormat format) { 138 addPageLabel(format.physicalPage, format.numberStyle, format.prefix, format.logicalPage); 139 } 140 141 /** Removes a page label. The first page label can not be removed, only changed. 142 * @param page the real page to remove 143 */ 144 public void removePageLabel(int page) { 145 if (page <= 1) 146 return; 147 map.remove(Integer.valueOf(page - 1)); 148 } 149 150 /** Gets the page label dictionary to insert into the document. 151 * @return the page label dictionary 152 */ 153 PdfDictionary getDictionary(PdfWriter writer) { 154 try { 155 return PdfNumberTree.writeTree(map, writer); 156 } 157 catch (IOException e) { 158 throw new ExceptionConverter(e); 159 } 160 } 161 162 /** 163 * Retrieves the page labels from a PDF as an array of String objects. 164 * @param reader a PdfReader object that has the page labels you want to retrieve 165 * @return a String array or <code>null</code> if no page labels are present 166 */ 167 public static String[] getPageLabels(PdfReader reader) { 168 169 int n = reader.getNumberOfPages(); 170 171 PdfDictionary dict = reader.getCatalog(); 172 PdfDictionary labels = (PdfDictionary)PdfReader.getPdfObjectRelease(dict.get(PdfName.PAGELABELS)); 173 if (labels == null) 174 return null; 175 176 String[] labelstrings = new String[n]; 177 178 HashMap<Integer, PdfObject> numberTree = PdfNumberTree.readTree(labels); 179 180 int pagecount = 1; 181 Integer current; 182 String prefix = ""; 183 char type = 'D'; 184 for (int i = 0; i < n; i++) { 185 current = Integer.valueOf(i); 186 if (numberTree.containsKey(current)) { 187 PdfDictionary d = (PdfDictionary)PdfReader.getPdfObjectRelease(numberTree.get(current)); 188 if (d.contains(PdfName.ST)) { 189 pagecount = ((PdfNumber)d.get(PdfName.ST)).intValue(); 190 } 191 else { 192 pagecount = 1; 193 } 194 if (d.contains(PdfName.P)) { 195 prefix = ((PdfString)d.get(PdfName.P)).toUnicodeString(); 196 } 197 if (d.contains(PdfName.S)) { 198 type = ((PdfName)d.get(PdfName.S)).toString().charAt(1); 199 } 200 } 201 switch(type) { 202 default: 203 labelstrings[i] = prefix + pagecount; 204 break; 205 case 'R': 206 labelstrings[i] = prefix + RomanNumberFactory.getUpperCaseString(pagecount); 207 break; 208 case 'r': 209 labelstrings[i] = prefix + RomanNumberFactory.getLowerCaseString(pagecount); 210 break; 211 case 'A': 212 labelstrings[i] = prefix + RomanAlphabetFactory.getUpperCaseString(pagecount); 213 break; 214 case 'a': 215 labelstrings[i] = prefix + RomanAlphabetFactory.getLowerCaseString(pagecount); 216 break; 217 } 218 pagecount++; 219 } 220 return labelstrings; 221 } 222 223 /** 224 * Retrieves the page labels from a PDF as an array of {@link PdfPageLabelFormat} objects. 225 * @param reader a PdfReader object that has the page labels you want to retrieve 226 * @return a PdfPageLabelEntry array, containing an entry for each format change 227 * or <code>null</code> if no page labels are present 228 */ 229 public static PdfPageLabelFormat[] getPageLabelFormats(PdfReader reader) { 230 PdfDictionary dict = reader.getCatalog(); 231 PdfDictionary labels = (PdfDictionary)PdfReader.getPdfObjectRelease(dict.get(PdfName.PAGELABELS)); 232 if (labels == null) 233 return null; 234 HashMap<Integer, PdfObject> numberTree = PdfNumberTree.readTree(labels); 235 Integer numbers[] = new Integer[numberTree.size()]; 236 numbers = numberTree.keySet().toArray(numbers); 237 Arrays.sort(numbers); 238 PdfPageLabelFormat[] formats = new PdfPageLabelFormat[numberTree.size()]; 239 String prefix; 240 int numberStyle; 241 int pagecount; 242 for (int k = 0; k < numbers.length; ++k) { 243 Integer key = numbers[k]; 244 PdfDictionary d = (PdfDictionary)PdfReader.getPdfObjectRelease(numberTree.get(key)); 245 if (d.contains(PdfName.ST)) { 246 pagecount = ((PdfNumber)d.get(PdfName.ST)).intValue(); 247 } else { 248 pagecount = 1; 249 } 250 if (d.contains(PdfName.P)) { 251 prefix = ((PdfString)d.get(PdfName.P)).toUnicodeString(); 252 } else { 253 prefix = ""; 254 } 255 if (d.contains(PdfName.S)) { 256 char type = ((PdfName)d.get(PdfName.S)).toString().charAt(1); 257 switch(type) { 258 case 'R': numberStyle = UPPERCASE_ROMAN_NUMERALS; break; 259 case 'r': numberStyle = LOWERCASE_ROMAN_NUMERALS; break; 260 case 'A': numberStyle = UPPERCASE_LETTERS; break; 261 case 'a': numberStyle = LOWERCASE_LETTERS; break; 262 default: numberStyle = DECIMAL_ARABIC_NUMERALS; break; 263 } 264 } else { 265 numberStyle = EMPTY; 266 } 267 formats[k] = new PdfPageLabelFormat(key.intValue()+1, numberStyle, prefix, pagecount); 268 } 269 return formats; 270 } 271 272 public static class PdfPageLabelFormat { 273 274 public int physicalPage; 275 public int numberStyle; 276 public String prefix; 277 public int logicalPage; 278 279 /** Creates a page label format. 280 * @param physicalPage the real page to start the numbering. First page is 1 281 * @param numberStyle the numbering style such as LOWERCASE_ROMAN_NUMERALS 282 * @param prefix the text to prefix the number. Can be <CODE>null</CODE> or empty 283 * @param logicalPage the first logical page number 284 */ 285 public PdfPageLabelFormat(int physicalPage, int numberStyle, String prefix, int logicalPage) { 286 this.physicalPage = physicalPage; 287 this.numberStyle = numberStyle; 288 this.prefix = prefix; 289 this.logicalPage = logicalPage; 290 } 291 } 292}