/* ===================================================================== * OrsonPDF : a fast, light-weight PDF library for the Java(tm) platform * ===================================================================== * * (C)opyright 2013-2015, by Object Refinery Limited. All rights reserved. * * http://www.object-refinery.com/orsonpdf/index.html * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. * Other names may be trademarks of their respective owners.] * * If you do not wish to be bound by the terms of the GPL, an alternative * commercial license can be purchased. For details, please see visit the * Orson PDF home page: * * http://www.object-refinery.com/orsonpdf/index.html * */ package com.orsonpdf; import java.awt.geom.Rectangle2D; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.HashMap; import java.util.Map; /** * A dictionary is a map and supports writing the bytes for the dictionary * in the PDF syntax. The dictionary has an optional {@code type} entry * which is treated as a special case (to ensure it comes first in the output * if it is specified). */ public class Dictionary { /** * The type entry. We treat this as a special case, because when a type is * defined, we want it to appear first in the PDF output. Note that it * can be set to null for some dictionaries. */ private String type; /** Data storage. */ private Map map; /** * Creates a new instance with no type. */ public Dictionary() { this(null); } /** * Creates a new dictionary with the specified type (which can be * {@code null}). * * @param type the type value (for example, "/Catalog"). */ public Dictionary(String type) { this.type = type; this.map = new HashMap(); } /** * Returns the dictionary type. * * @return The dictionary type (possibly ({@code null}). */ public String getType() { return this.type; } /** * Sets the type (for example, "/Catalog"). * * @param type the type ({@code null} permitted). */ public void setType(String type) { this.type = type; } /** * Returns {@code true} if the dictionary has no entries, and * {@code false} otherwise. * * @return A boolean. * * @see #size() */ public boolean isEmpty() { return this.map.isEmpty(); } /** * Returns the number of items in the dictionary. * * @return The number of items in the dictionary. */ public int size() { return this.map.size(); } /** * Puts an entry in the dictionary. * * @param key the key. * @param value the value. */ public void put(String key, Object value) { this.map.put(key, value); } /** * Removes an entry from the dictionary, returning the value that was * stored previously. * * @param key the key. * * @return The value that was associated with the key. */ public Object remove(String key) { return this.map.remove(key); } /** * Returns a byte array containing the ASCII encoding of the dictionary. * * @return A byte array. */ public byte[] toPDFBytes() { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { // here we are first creating the String version, then encoding // to bytes...it would be more efficient to go direct to bytes // but this will do for now baos.write(PDFUtils.toBytes(toPDFString())); } catch (IOException ex) { throw new RuntimeException("Dictionary.toPDFBytes() failed.", ex); } return baos.toByteArray(); } /** * Returns a string containing the PDF text describing the dictionary. * Note that this is a Java string, conversion to byte format happens * elsewhere. * * @return A string. */ public String toPDFString() { StringBuilder b = new StringBuilder(); b.append("<< "); if (this.type != null) { b.append("/Type ").append(this.type).append("\n"); } // now iterate through the dictionary and write its values for (Object key : this.map.keySet()) { Object value = this.map.get(key); if (value instanceof Number || value instanceof String) { b.append(key.toString()).append(" "); b.append(value.toString()).append("\n"); } else if (value instanceof PDFObject) { PDFObject pdfObj = (PDFObject) value; b.append(key.toString()).append(" "); b.append(pdfObj.getReference()).append("\n"); } else if (value instanceof String[]) { b.append(key.toString()).append(" "); String[] array = (String[]) value; b.append("["); for (int i = 0; i < array.length; i++) { if (i != 0) { b.append(" "); } b.append(array[i]); } b.append("]\n"); } else if (value instanceof PDFObject[]) { b.append(key.toString()).append(" "); PDFObject[] array = (PDFObject[]) value; b.append("["); for (int i = 0; i < array.length; i++) { if (i != 0) { b.append(" "); } b.append(array[i].getReference()); } b.append("]\n"); } else if (value instanceof Rectangle2D) { Rectangle2D r = (Rectangle2D) value; b.append(key.toString()).append(" "); b.append("[").append(r.getX()).append(" "); b.append(r.getY()).append(" ").append(r.getWidth()).append(" "); b.append(r.getHeight()).append("]\n"); } else if (value instanceof Dictionary) { b.append(key.toString()).append(" "); Dictionary d = (Dictionary) value; b.append(d.toPDFString()); } else if (value instanceof float[]) { b.append(key.toString()).append(" "); float[] array = (float[]) value; b.append("["); for (int i = 0; i < array.length; i++) { if (i != 0) { b.append(" "); } b.append(array[i]); } b.append("]\n"); } else { throw new RuntimeException("Unrecognised value type: " + value); } } b.append(">>\n"); return b.toString(); } }