/* * 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$ */packageorg.apache.fop.render.pdf;importjava.awt.color.ICC_Profile;importjava.io.IOException;importjava.io.InputStream;importjava.io.OutputStream;importjava.net.URL;importjava.util.Map;importjavax.xml.transform.Source;importjavax.xml.transform.stream.StreamSource;importorg.apache.commons.io.IOUtils;importorg.apache.commons.logging.Log;importorg.apache.commons.logging.LogFactory;importorg.apache.xmlgraphics.xmp.Metadata;importorg.apache.xmlgraphics.xmp.schemas.XMPBasicAdapter;importorg.apache.xmlgraphics.xmp.schemas.XMPBasicSchema;importorg.apache.fop.accessibility.AccessibilityUtil;importorg.apache.fop.apps.FOUserAgent;importorg.apache.fop.fo.extensions.xmp.XMPMetadata;importorg.apache.fop.pdf.PDFAMode;importorg.apache.fop.pdf.PDFConformanceException;importorg.apache.fop.pdf.PDFDictionary;importorg.apache.fop.pdf.PDFDocument;importorg.apache.fop.pdf.PDFEncryptionManager;importorg.apache.fop.pdf.PDFEncryptionParams;importorg.apache.fop.pdf.PDFICCBasedColorSpace;importorg.apache.fop.pdf.PDFICCStream;importorg.apache.fop.pdf.PDFInfo;importorg.apache.fop.pdf.PDFMetadata;importorg.apache.fop.pdf.PDFNumsArray;importorg.apache.fop.pdf.PDFOutputIntent;importorg.apache.fop.pdf.PDFPageLabels;importorg.apache.fop.pdf.PDFXMode;importorg.apache.fop.util.ColorProfileUtil;/** * Utility class which enables all sorts of features that are not directly connected to the * normal rendering process. */classPDFRenderingUtilimplementsPDFConfigurationConstants{/** logging instance */privatestaticLoglog=LogFactory.getLog(PDFRenderingUtil.class);privateFOUserAgentuserAgent;/** the PDF Document being created */protectedPDFDocumentpdfDoc;/** the PDF/A mode (Default: disabled) */protectedPDFAModepdfAMode=PDFAMode.DISABLED;/** the PDF/X mode (Default: disabled) */protectedPDFXModepdfXMode=PDFXMode.DISABLED;/** the (optional) encryption parameters */protectedPDFEncryptionParamsencryptionParams;/** Registry of PDF filters */protectedMapfilterMap;/** the ICC stream used as output profile by this document for PDF/A and PDF/X functionality. */protectedPDFICCStreamoutputProfile;/** the default sRGB color space. */protectedPDFICCBasedColorSpacesRGBColorSpace;/** controls whether the sRGB color space should be installed */protectedbooleandisableSRGBColorSpace=false;/** Optional URI to an output profile to be used. */protectedStringoutputProfileURI;PDFRenderingUtil(FOUserAgentuserAgent){this.userAgent=userAgent;initialize();}privatestaticbooleanbooleanValueOf(Objectobj){if(objinstanceofBoolean){return((Boolean)obj).booleanValue();}elseif(objinstanceofString){returnBoolean.valueOf((String)obj).booleanValue();}else{thrownewIllegalArgumentException("Boolean or \"true\" or \"false\" expected.");}}privatevoidinitialize(){PDFEncryptionParamsparams=(PDFEncryptionParams)userAgent.getRendererOptions().get(ENCRYPTION_PARAMS);if(params!=null){this.encryptionParams=params;//overwrite if available}Stringpwd;pwd=(String)userAgent.getRendererOptions().get(USER_PASSWORD);if(pwd!=null){if(encryptionParams==null){this.encryptionParams=newPDFEncryptionParams();}this.encryptionParams.setUserPassword(pwd);}pwd=(String)userAgent.getRendererOptions().get(OWNER_PASSWORD);if(pwd!=null){if(encryptionParams==null){this.encryptionParams=newPDFEncryptionParams();}this.encryptionParams.setOwnerPassword(pwd);}Objectsetting;setting=userAgent.getRendererOptions().get(NO_PRINT);if(setting!=null){if(encryptionParams==null){this.encryptionParams=newPDFEncryptionParams();}this.encryptionParams.setAllowPrint(!booleanValueOf(setting));}setting=userAgent.getRendererOptions().get(NO_COPY_CONTENT);if(setting!=null){if(encryptionParams==null){this.encryptionParams=newPDFEncryptionParams();}this.encryptionParams.setAllowCopyContent(!booleanValueOf(setting));}setting=userAgent.getRendererOptions().get(NO_EDIT_CONTENT);if(setting!=null){if(encryptionParams==null){this.encryptionParams=newPDFEncryptionParams();}this.encryptionParams.setAllowEditContent(!booleanValueOf(setting));}setting=userAgent.getRendererOptions().get(NO_ANNOTATIONS);if(setting!=null){if(encryptionParams==null){this.encryptionParams=newPDFEncryptionParams();}this.encryptionParams.setAllowEditAnnotations(!booleanValueOf(setting));}Strings=(String)userAgent.getRendererOptions().get(PDF_A_MODE);if(s!=null){this.pdfAMode=PDFAMode.valueOf(s);}if(this.pdfAMode.isPDFA1LevelA()){//Enable accessibility if PDF/A-1a is enabled because it requires tagged PDF.userAgent.getRendererOptions().put(AccessibilityUtil.ACCESSIBILITY,Boolean.TRUE);}s=(String)userAgent.getRendererOptions().get(PDF_X_MODE);if(s!=null){this.pdfXMode=PDFXMode.valueOf(s);}s=(String)userAgent.getRendererOptions().get(KEY_OUTPUT_PROFILE);if(s!=null){this.outputProfileURI=s;}setting=userAgent.getRendererOptions().get(KEY_DISABLE_SRGB_COLORSPACE);if(setting!=null){this.disableSRGBColorSpace=booleanValueOf(setting);}}publicFOUserAgentgetUserAgent(){returnthis.userAgent;}/** * Sets the PDF/A mode for the PDF renderer. * @param mode the PDF/A mode */publicvoidsetAMode(PDFAModemode){this.pdfAMode=mode;}/** * Sets the PDF/X mode for the PDF renderer. * @param mode the PDF/X mode */publicvoidsetXMode(PDFXModemode){this.pdfXMode=mode;}/** * Sets the output color profile for the PDF renderer. * @param outputProfileURI the URI to the output color profile */publicvoidsetOutputProfileURI(StringoutputProfileURI){this.outputProfileURI=outputProfileURI;}/** * Enables or disables the default sRGB color space needed for the PDF document to preserve * the sRGB colors used in XSL-FO. * @param disable true to disable, false to enable */publicvoidsetDisableSRGBColorSpace(booleandisable){this.disableSRGBColorSpace=disable;}/** * Sets the filter map to be used by the PDF renderer. * @param filterMap the filter map */publicvoidsetFilterMap(MapfilterMap){this.filterMap=filterMap;}/** * Sets the encryption parameters used by the PDF renderer. * @param encryptionParams the encryption parameters */publicvoidsetEncryptionParams(PDFEncryptionParamsencryptionParams){this.encryptionParams=encryptionParams;}privatevoidupdateInfo(){PDFInfoinfo=pdfDoc.getInfo();info.setCreator(userAgent.getCreator());info.setCreationDate(userAgent.getCreationDate());info.setAuthor(userAgent.getAuthor());info.setTitle(userAgent.getTitle());info.setSubject(userAgent.getSubject());info.setKeywords(userAgent.getKeywords());}privatevoidupdatePDFProfiles(){pdfDoc.getProfile().setPDFAMode(this.pdfAMode);pdfDoc.getProfile().setPDFXMode(this.pdfXMode);}privatevoidaddsRGBColorSpace()throwsIOException{if(disableSRGBColorSpace){if(this.pdfAMode!=PDFAMode.DISABLED||this.pdfXMode!=PDFXMode.DISABLED||this.outputProfileURI!=null){thrownewIllegalStateException("It is not possible to disable the sRGB color"+" space if PDF/A or PDF/X functionality is enabled or an"+" output profile is set!");}}else{if(this.sRGBColorSpace!=null){return;}//Map sRGB as default RGB profile for DeviceRGBthis.sRGBColorSpace=PDFICCBasedColorSpace.setupsRGBAsDefaultRGBColorSpace(pdfDoc);}}privatevoidaddDefaultOutputProfile()throwsIOException{if(this.outputProfile!=null){return;}ICC_Profileprofile;InputStreamin=null;if(this.outputProfileURI!=null){this.outputProfile=pdfDoc.getFactory().makePDFICCStream();Sourcesrc=getUserAgent().resolveURI(this.outputProfileURI);if(src==null){thrownewIOException("Output profile not found: "+this.outputProfileURI);}if(srcinstanceofStreamSource){in=((StreamSource)src).getInputStream();}else{in=newURL(src.getSystemId()).openStream();}try{profile=ICC_Profile.getInstance(in);}finally{IOUtils.closeQuietly(in);}this.outputProfile.setColorSpace(profile,null);}else{//Fall back to sRGB profileoutputProfile=sRGBColorSpace.getICCStream();}}/** * Adds an OutputIntent to the PDF as mandated by PDF/A-1 when uncalibrated color spaces * are used (which is true if we use DeviceRGB to represent sRGB colors). * @throws IOException in case of an I/O problem */privatevoidaddPDFA1OutputIntent()throwsIOException{addDefaultOutputProfile();Stringdesc=ColorProfileUtil.getICCProfileDescription(this.outputProfile.getICCProfile());PDFOutputIntentoutputIntent=pdfDoc.getFactory().makeOutputIntent();outputIntent.setSubtype(PDFOutputIntent.GTS_PDFA1);outputIntent.setDestOutputProfile(this.outputProfile);outputIntent.setOutputConditionIdentifier(desc);outputIntent.setInfo(outputIntent.getOutputConditionIdentifier());pdfDoc.getRoot().addOutputIntent(outputIntent);}/** * Adds an OutputIntent to the PDF as mandated by PDF/X when uncalibrated color spaces * are used (which is true if we use DeviceRGB to represent sRGB colors). * @throws IOException in case of an I/O problem */privatevoidaddPDFXOutputIntent()throwsIOException{addDefaultOutputProfile();Stringdesc=ColorProfileUtil.getICCProfileDescription(this.outputProfile.getICCProfile());intdeviceClass=this.outputProfile.getICCProfile().getProfileClass();if(deviceClass!=ICC_Profile.CLASS_OUTPUT){thrownewPDFConformanceException(pdfDoc.getProfile().getPDFXMode()+" requires that"+" the DestOutputProfile be an Output Device Profile. "+desc+" does not match that requirement.");}PDFOutputIntentoutputIntent=pdfDoc.getFactory().makeOutputIntent();outputIntent.setSubtype(PDFOutputIntent.GTS_PDFX);outputIntent.setDestOutputProfile(this.outputProfile);outputIntent.setOutputConditionIdentifier(desc);outputIntent.setInfo(outputIntent.getOutputConditionIdentifier());pdfDoc.getRoot().addOutputIntent(outputIntent);}publicvoidrenderXMPMetadata(XMPMetadatametadata){MetadatadocXMP=metadata.getMetadata();MetadatafopXMP=PDFMetadata.createXMPFromPDFDocument(pdfDoc);//Merge FOP's own metadata into the one from the XSL-FO documentfopXMP.mergeInto(docXMP);XMPBasicAdapterxmpBasic=XMPBasicSchema.getAdapter(docXMP);//Metadata was changed so update metadata datexmpBasic.setMetadataDate(newjava.util.Date());PDFMetadata.updateInfoFromMetadata(docXMP,pdfDoc.getInfo());PDFMetadatapdfMetadata=pdfDoc.getFactory().makeMetadata(docXMP,metadata.isReadOnly());pdfDoc.getRoot().setMetadata(pdfMetadata);}publicvoidgenerateDefaultXMPMetadata(){if(pdfDoc.getRoot().getMetadata()==null){//If at this time no XMP metadata for the overall document has been set, create it//from the PDFInfo object.Metadataxmp=PDFMetadata.createXMPFromPDFDocument(pdfDoc);PDFMetadatapdfMetadata=pdfDoc.getFactory().makeMetadata(xmp,true);pdfDoc.getRoot().setMetadata(pdfMetadata);}}publicPDFDocumentsetupPDFDocument(OutputStreamout)throwsIOException{if(this.pdfDoc!=null){thrownewIllegalStateException("PDFDocument already set up");}this.pdfDoc=newPDFDocument(userAgent.getProducer()!=null?userAgent.getProducer():"");updateInfo();updatePDFProfiles();pdfDoc.setFilterMap(filterMap);pdfDoc.outputHeader(out);//Setup encryption if necessaryPDFEncryptionManager.setupPDFEncryption(encryptionParams,pdfDoc);addsRGBColorSpace();if(this.outputProfileURI!=null){addDefaultOutputProfile();}if(pdfXMode!=PDFXMode.DISABLED){log.debug(pdfXMode+" is active.");log.warn("Note: "+pdfXMode+" support is work-in-progress and not fully implemented, yet!");addPDFXOutputIntent();}if(pdfAMode.isPDFA1LevelB()){log.debug("PDF/A is active. Conformance Level: "+pdfAMode);addPDFA1OutputIntent();}returnthis.pdfDoc;}/** * Generates a page label in the PDF document. * @param pageIndex the index of the page * @param pageNumber the formatted page number */publicvoidgeneratePageLabel(intpageIndex,StringpageNumber){//Produce page labelsPDFPageLabelspageLabels=this.pdfDoc.getRoot().getPageLabels();if(pageLabels==null){//Set up PageLabelspageLabels=this.pdfDoc.getFactory().makePageLabels();this.pdfDoc.getRoot().setPageLabels(pageLabels);}PDFNumsArraynums=pageLabels.getNums();PDFDictionarydict=newPDFDictionary(nums);dict.put("P",pageNumber);//TODO If the sequence of generated page numbers were inspected, this could be//expressed in a more space-efficient waynums.put(pageIndex,dict);}}