001/*
002 * $Id: XmpWriter.java 4887 2011-05-29 14:47:50Z 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.xml.xmp;
045
046import java.io.IOException;
047import java.io.OutputStream;
048import java.io.OutputStreamWriter;
049import java.util.Map;
050
051import com.itextpdf.text.pdf.PdfDate;
052import com.itextpdf.text.pdf.PdfDictionary;
053import com.itextpdf.text.pdf.PdfName;
054import com.itextpdf.text.pdf.PdfObject;
055import com.itextpdf.text.pdf.PdfString;
056import com.itextpdf.text.pdf.PdfWriter;
057
058/**
059 * With this class you can create an Xmp Stream that can be used for adding
060 * Metadata to a PDF Dictionary. Remark that this class doesn't cover the
061 * complete XMP specification.
062 */
063public class XmpWriter {
064
065        /** A possible charset for the XMP. */
066        public static final String UTF8 = "UTF-8";
067        /** A possible charset for the XMP. */
068        public static final String UTF16 = "UTF-16";
069        /** A possible charset for the XMP. */
070        public static final String UTF16BE = "UTF-16BE";
071        /** A possible charset for the XMP. */
072        public static final String UTF16LE = "UTF-16LE";
073
074        /** String used to fill the extra space. */
075        public static final String EXTRASPACE = "                                                                                                   \n";
076
077        /** You can add some extra space in the XMP packet; 1 unit in this variable represents 100 spaces and a newline. */
078        protected int extraSpace;
079
080        /** The writer to which you can write bytes for the XMP stream. */
081        protected OutputStreamWriter writer;
082
083        /** The about string that goes into the rdf:Description tags. */
084        protected String about;
085
086        /**
087         * Processing Instruction required at the start of an XMP stream
088         * @since iText 2.1.6
089         */
090        public static final String XPACKET_PI_BEGIN = "<?xpacket begin=\"\uFEFF\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>\n";
091
092        /**
093         * Processing Instruction required at the end of an XMP stream for XMP streams that can be updated
094         * @since iText 2.1.6
095         */
096        public static final String XPACKET_PI_END_W = "<?xpacket end=\"w\"?>";
097
098        /**
099         * Processing Instruction required at the end of an XMP stream for XMP streams that are read only
100         * @since iText 2.1.6
101         */
102        public static final String XPACKET_PI_END_R = "<?xpacket end=\"r\"?>";
103
104        /** The end attribute. */
105        protected char end = 'w';
106
107        /**
108         * Creates an XmpWriter.
109         * @param os
110         * @param utfEncoding
111         * @param extraSpace
112         * @throws IOException
113         */
114        public XmpWriter(OutputStream os, String utfEncoding, int extraSpace) throws IOException {
115                this.extraSpace = extraSpace;
116                writer = new OutputStreamWriter(os, utfEncoding);
117                writer.write(XPACKET_PI_BEGIN);
118                writer.write("<x:xmpmeta xmlns:x=\"adobe:ns:meta/\">\n");
119                writer.write("<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n");
120                about = "";
121        }
122
123        /**
124         * Creates an XmpWriter.
125         * @param os
126         * @throws IOException
127         */
128        public XmpWriter(OutputStream os) throws IOException {
129                this(os, UTF8, 20);
130        }
131
132        /** Sets the XMP to read-only */
133        public void setReadOnly() {
134                end = 'r';
135        }
136
137        /**
138         * @param about The about to set.
139         */
140        public void setAbout(String about) {
141                this.about = about;
142        }
143
144        /**
145         * Adds an rdf:Description.
146         * @param xmlns
147         * @param content
148         * @throws IOException
149         */
150        public void addRdfDescription(String xmlns, String content) throws IOException {
151                writer.write("<rdf:Description rdf:about=\"");
152                writer.write(about);
153                writer.write("\" ");
154                writer.write(xmlns);
155                writer.write(">");
156                writer.write(content);
157                writer.write("</rdf:Description>\n");
158        }
159
160        /**
161         * Adds an rdf:Description.
162         * @param s
163         * @throws IOException
164         */
165        public void addRdfDescription(XmpSchema s) throws IOException {
166                writer.write("<rdf:Description rdf:about=\"");
167                writer.write(about);
168                writer.write("\" ");
169                writer.write(s.getXmlns());
170                writer.write(">");
171                writer.write(s.toString());
172                writer.write("</rdf:Description>\n");
173        }
174
175        /**
176         * Flushes and closes the XmpWriter.
177         * @throws IOException
178         */
179        public void close() throws IOException {
180                writer.write("</rdf:RDF>");
181                writer.write("</x:xmpmeta>\n");
182                for (int i = 0; i < extraSpace; i++) {
183                        writer.write(EXTRASPACE);
184                }
185                writer.write(end == 'r' ? XPACKET_PI_END_R : XPACKET_PI_END_W);
186                writer.flush();
187                writer.close();
188        }
189
190    /**
191     * @param os
192     * @param info
193     * @throws IOException
194     */
195    public XmpWriter(OutputStream os, PdfDictionary info, int PdfXConformance) throws IOException {
196        this(os);
197        if (info != null) {
198                DublinCoreSchema dc = new DublinCoreSchema();
199                PdfSchema p = new PdfSchema();
200                XmpBasicSchema basic = new XmpBasicSchema();
201                PdfName key;
202                PdfObject obj;
203                for (PdfName pdfName : info.getKeys()) {
204                        key = pdfName;
205                        obj = info.get(key);
206                        if (obj == null)
207                                continue;
208                        if (PdfName.TITLE.equals(key)) {
209                                dc.addTitle(((PdfString)obj).toUnicodeString());
210                        }
211                        if (PdfName.AUTHOR.equals(key)) {
212                                dc.addAuthor(((PdfString)obj).toUnicodeString());
213                        }
214                        if (PdfName.SUBJECT.equals(key)) {
215                                dc.addSubject(((PdfString)obj).toUnicodeString());
216                                dc.addDescription(((PdfString)obj).toUnicodeString());
217                        }
218                        if (PdfName.KEYWORDS.equals(key)) {
219                                p.addKeywords(((PdfString)obj).toUnicodeString());
220                        }
221                        if (PdfName.CREATOR.equals(key)) {
222                                basic.addCreatorTool(((PdfString)obj).toUnicodeString());
223                        }
224                        if (PdfName.PRODUCER.equals(key)) {
225                                p.addProducer(((PdfString)obj).toUnicodeString());
226                        }
227                        if (PdfName.CREATIONDATE.equals(key)) {
228                                basic.addCreateDate(PdfDate.getW3CDate(obj.toString()));
229                        }
230                        if (PdfName.MODDATE.equals(key)) {
231                                basic.addModDate(PdfDate.getW3CDate(obj.toString()));
232                        }
233                }
234                if (dc.size() > 0) addRdfDescription(dc);
235                if (p.size() > 0) addRdfDescription(p);
236                if (basic.size() > 0) addRdfDescription(basic);
237            if (PdfXConformance == PdfWriter.PDFA1A || PdfXConformance == PdfWriter.PDFA1B) {
238                PdfA1Schema a1 = new PdfA1Schema();
239                if (PdfXConformance == PdfWriter.PDFA1A)
240                    a1.addConformance("A");
241                else
242                    a1.addConformance("B");
243                addRdfDescription(a1);
244            }
245        }
246    }
247
248    /**
249     * @param os
250     * @param info
251     * @throws IOException
252     * @since 5.0.1 (generic type in signature)
253     */
254    public XmpWriter(OutputStream os, Map<String, String> info) throws IOException {
255        this(os);
256        if (info != null) {
257                DublinCoreSchema dc = new DublinCoreSchema();
258                PdfSchema p = new PdfSchema();
259                XmpBasicSchema basic = new XmpBasicSchema();
260                String key;
261                String value;
262                for (Map.Entry<String, String> entry: info.entrySet()) {
263                        key = entry.getKey();
264                        value = entry.getValue();
265                        if (value == null)
266                                continue;
267                        if ("Title".equals(key)) {
268                                dc.addTitle(value);
269                        }
270                        if ("Author".equals(key)) {
271                                dc.addAuthor(value);
272                        }
273                        if ("Subject".equals(key)) {
274                                dc.addSubject(value);
275                                dc.addDescription(value);
276                        }
277                        if ("Keywords".equals(key)) {
278                                p.addKeywords(value);
279                        }
280                        if ("Creator".equals(key)) {
281                                basic.addCreatorTool(value);
282                        }
283                        if ("Producer".equals(key)) {
284                                p.addProducer(value);
285                        }
286                        if ("CreationDate".equals(key)) {
287                                basic.addCreateDate(PdfDate.getW3CDate(value));
288                        }
289                        if ("ModDate".equals(key)) {
290                                basic.addModDate(PdfDate.getW3CDate(value));
291                        }
292                }
293                if (dc.size() > 0) addRdfDescription(dc);
294                if (p.size() > 0) addRdfDescription(p);
295                if (basic.size() > 0) addRdfDescription(basic);
296        }
297    }
298}