/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* $Id: PDFMetadata.java 1695313 2015-08-11 14:43:08Z ssteiner $ */

package org.apache.fop.pdf;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Date;
import java.util.UUID;

import javax.xml.transform.TransformerConfigurationException;

import org.xml.sax.SAXException;

import org.apache.xmlgraphics.xmp.Metadata;
import org.apache.xmlgraphics.xmp.XMPSerializer;
import org.apache.xmlgraphics.xmp.schemas.DublinCoreAdapter;
import org.apache.xmlgraphics.xmp.schemas.DublinCoreSchema;
import org.apache.xmlgraphics.xmp.schemas.XMPBasicAdapter;
import org.apache.xmlgraphics.xmp.schemas.XMPBasicSchema;
import org.apache.xmlgraphics.xmp.schemas.pdf.AdobePDFAdapter;
import org.apache.xmlgraphics.xmp.schemas.pdf.AdobePDFSchema;
import org.apache.xmlgraphics.xmp.schemas.pdf.PDFAAdapter;
import org.apache.xmlgraphics.xmp.schemas.pdf.PDFAXMPSchema;
import org.apache.xmlgraphics.xmp.schemas.pdf.PDFUAAdapter;
import org.apache.xmlgraphics.xmp.schemas.pdf.PDFUAXMPSchema;
import org.apache.xmlgraphics.xmp.schemas.pdf.PDFVTAdapter;
import org.apache.xmlgraphics.xmp.schemas.pdf.PDFVTXMPSchema;
import org.apache.xmlgraphics.xmp.schemas.pdf.PDFXAdapter;
import org.apache.xmlgraphics.xmp.schemas.pdf.PDFXXMPSchema;
import org.apache.xmlgraphics.xmp.schemas.pdf.XAPMMAdapter;
import org.apache.xmlgraphics.xmp.schemas.pdf.XAPMMXMPSchema;

Special PDFStream for Metadata.
Since:PDF 1.4
/** * Special PDFStream for Metadata. * @since PDF 1.4 */
public class PDFMetadata extends PDFStream { private Metadata xmpMetadata; private boolean readOnly = true;
Params:
  • xmp – xmp metadata
  • readOnly – true if read only
See Also:
/** * @param xmp xmp metadata * @param readOnly true if read only * @see org.apache.fop.pdf.PDFObject#PDFObject() */
public PDFMetadata(Metadata xmp, boolean readOnly) { super(); if (xmp == null) { throw new NullPointerException( "The parameter for the XMP Document must not be null"); } this.xmpMetadata = xmp; this.readOnly = readOnly; }
{@inheritDoc}
/** {@inheritDoc} */
protected String getDefaultFilterName() { return PDFFilterList.METADATA_FILTER; }
Returns:the XMP metadata
/** * @return the XMP metadata */
public Metadata getMetadata() { return this.xmpMetadata; }
overload the base object method so we don't have to copy byte arrays around so much {@inheritDoc}
/** * overload the base object method so we don't have to copy * byte arrays around so much * {@inheritDoc} */
public int output(java.io.OutputStream stream) throws java.io.IOException { int length = super.output(stream); this.xmpMetadata = null; //Release DOM when it's not used anymore return length; }
{@inheritDoc}
/** {@inheritDoc} */
protected void outputRawStreamData(OutputStream out) throws IOException { try { XMPSerializer.writeXMPPacket(xmpMetadata, out, this.readOnly); } catch (TransformerConfigurationException tce) { throw new IOException("Error setting up Transformer for XMP stream serialization: " + tce.getMessage()); } catch (SAXException saxe) { throw new IOException("Error while serializing XMP stream: " + saxe.getMessage()); } }
{@inheritDoc}
/** {@inheritDoc} */
protected void populateStreamDict(Object lengthEntry) { final String filterEntry = getFilterList().buildFilterDictEntries(); if (getDocumentSafely().getProfile().getPDFAMode().isPart1() && filterEntry != null && filterEntry.length() > 0) { throw new PDFConformanceException( "The Filter key is prohibited when PDF/A-1 is active"); } put("Type", new PDFName("Metadata")); put("Subtype", new PDFName("XML")); super.populateStreamDict(lengthEntry); }
Creates an XMP document based on the settings on the PDF Document.
Params:
  • pdfDoc – the PDF Document
Returns:the requested XMP metadata
/** * Creates an XMP document based on the settings on the PDF Document. * @param pdfDoc the PDF Document * @return the requested XMP metadata */
public static Metadata createXMPFromPDFDocument(PDFDocument pdfDoc) { Metadata meta = new Metadata(); PDFInfo info = pdfDoc.getInfo(); PDFRoot root = pdfDoc.getRoot(); //Set creation date if not available, yet if (info.getCreationDate() == null) { Date d = new Date(); info.setCreationDate(d); } //Important: Acrobat 7's preflight check for PDF/A-1b wants the creation date in the Info //object and in the XMP metadata to have the same timezone or else it shows a validation //error even if the times are essentially equal. //Dublin Core DublinCoreAdapter dc = DublinCoreSchema.getAdapter(meta); //PDF/A identification PDFAMode pdfaMode = pdfDoc.getProfile().getPDFAMode(); dc.setCompact(pdfaMode.getPart() != 3); if (info.getAuthor() != null) { dc.addCreator(info.getAuthor()); } if (info.getTitle() != null) { dc.setTitle(info.getTitle()); } if (info.getSubject() != null) { //Subject maps to dc:description["x-default"] as per ISO-19005-1:2005/Cor.1:2007 dc.setDescription(null, info.getSubject()); } if (root.getLanguage() != null) { //Note: No check is performed to make sure the value is valid RFC 3066! dc.addLanguage(root.getLanguage()); } dc.addDate(info.getCreationDate()); //Somewhat redundant but some PDF/A checkers issue a warning without this. dc.setFormat("application/pdf"); PDFUAMode pdfuaMode = pdfDoc.getProfile().getPDFUAMode(); if (pdfuaMode.isEnabled()) { PDFUAAdapter pdfua = PDFUAXMPSchema.getAdapter(meta); pdfua.setPart(pdfuaMode.getPart()); } if (pdfaMode.isEnabled()) { PDFAAdapter pdfa = PDFAXMPSchema.getAdapter(meta); pdfa.setPart(pdfaMode.getPart()); pdfa.setConformance(String.valueOf(pdfaMode.getConformanceLevel())); } AdobePDFAdapter adobePDF = AdobePDFSchema.getAdapter(meta); PDFXMode pdfxMode = pdfDoc.getProfile().getPDFXMode(); if (pdfxMode != PDFXMode.DISABLED) { PDFXAdapter pdfx = PDFXXMPSchema.getAdapter(meta); pdfx.setVersion(pdfxMode.getName()); XAPMMAdapter xapmm = XAPMMXMPSchema.getAdapter(meta); xapmm.setVersion("1"); xapmm.setDocumentID("uuid:" + UUID.randomUUID().toString()); xapmm.setInstanceID("uuid:" + UUID.randomUUID().toString()); xapmm.setRenditionClass("default"); adobePDF.setTrapped("False"); } PDFProfile profile = pdfDoc.getProfile(); PDFVTMode pdfvtMode = profile.getPDFVTMode(); if (pdfvtMode != PDFVTMode.DISABLED) { PDFVTAdapter pdfvt = PDFVTXMPSchema.getAdapter(meta); pdfvt.setVersion("PDF/VT-1"); if (info.getModDate() != null) { pdfvt.setModifyDate(info.getModDate()); } else if (profile.isModDateRequired()) { //if modify date is needed but none is in the Info object, use creation date pdfvt.setModifyDate(info.getCreationDate()); } } //XMP Basic Schema XMPBasicAdapter xmpBasic = XMPBasicSchema.getAdapter(meta); xmpBasic.setCreateDate(info.getCreationDate()); if (info.getModDate() != null) { xmpBasic.setModifyDate(info.getModDate()); } else if (profile.isModDateRequired()) { //if modify date is needed but none is in the Info object, use creation date xmpBasic.setModifyDate(info.getCreationDate()); } if (info.getCreator() != null) { xmpBasic.setCreatorTool(info.getCreator()); } if (info.getKeywords() != null) { adobePDF.setKeywords(info.getKeywords()); } if (info.getProducer() != null) { adobePDF.setProducer(info.getProducer()); } adobePDF.setPDFVersion(pdfDoc.getPDFVersionString()); return meta; }
Updates the values in the Info object from the XMP metadata according to the rules defined in PDF/A-1 (ISO 19005-1:2005)
Params:
  • meta – the metadata
  • info – the Info object
/** * Updates the values in the Info object from the XMP metadata according to the rules defined * in PDF/A-1 (ISO 19005-1:2005) * @param meta the metadata * @param info the Info object */
public static void updateInfoFromMetadata(Metadata meta, PDFInfo info) { DublinCoreAdapter dc = DublinCoreSchema.getAdapter(meta); info.setTitle(dc.getTitle()); String[] creators = dc.getCreators(); if (creators != null && creators.length > 0) { info.setAuthor(creators[0]); } else { info.setAuthor(null); } //dc:description["x-default"] maps to Subject as per ISO-19005-1:2005/Cor.1:2007 info.setSubject(dc.getDescription()); AdobePDFAdapter pdf = AdobePDFSchema.getAdapter(meta); info.setKeywords(pdf.getKeywords()); info.setProducer(pdf.getProducer()); XMPBasicAdapter xmpBasic = XMPBasicSchema.getAdapter(meta); info.setCreator(xmpBasic.getCreatorTool()); Date d; d = xmpBasic.getCreateDate(); xmpBasic.setCreateDate(d); //To make Adobe Acrobat happy (bug filed with Adobe) //Adobe Acrobat doesn't like it when the xmp:CreateDate has a different timezone //than Info/CreationDate info.setCreationDate(d); d = xmpBasic.getModifyDate(); if (d != null) { //ModifyDate is only required for PDF/X xmpBasic.setModifyDate(d); info.setModDate(d); } } }