/******************************************************************************* * Copyright 2010 Atos Worldline SAS * * Licensed by Atos Worldline SAS under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * Atos Worldline SAS 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. ******************************************************************************/ /** * */ package net.padaf.preflight.font; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import net.padaf.preflight.DocumentHandler; import net.padaf.preflight.ValidationConstants; import net.padaf.preflight.ValidationException; import net.padaf.preflight.ValidationResult.ValidationError; import net.padaf.preflight.font.AbstractFontContainer.State; import net.padaf.xmpbox.XMPMetadata; import net.padaf.xmpbox.parser.XMPDocumentBuilder; import net.padaf.xmpbox.parser.XmpExpectedRdfAboutAttribute; import net.padaf.xmpbox.parser.XmpParsingException; import net.padaf.xmpbox.parser.XmpSchemaException; import net.padaf.xmpbox.parser.XmpUnknownValueTypeException; import net.padaf.xmpbox.parser.XmpXpacketEndException; import net.padaf.xmpbox.type.BadFieldValueException; import org.apache.commons.io.IOUtils; import org.apache.pdfbox.cos.COSDictionary; import org.apache.pdfbox.cos.COSObject; import org.apache.pdfbox.pdmodel.common.PDMetadata; import org.apache.pdfbox.pdmodel.common.PDStream; import org.apache.pdfbox.pdmodel.font.PDFont; import org.apache.pdfbox.pdmodel.font.PDFontDescriptor; import org.apache.pdfbox.pdmodel.font.PDFontFactory; public abstract class AbstractFontValidator implements FontValidator,ValidationConstants { /** * DocumentHandler which contains all useful objects to validate a PDF/A ex : * parser JavaCC */ protected DocumentHandler handler = null; /** * The COSObject which is the starting point of the Font description in the * PDF/A file. This object should be an insteance of COSDictionary */ protected COSObject cObj = null; /** * The cObj casted as COSDictionary */ protected COSDictionary fDictionary = null; /** * The PdfBox font dictionary wrapper. */ protected PDFont pFont = null; /** * The Font Container contains the Font Validation state ( valid or not, * why...) This Font Container is tested when the font is used as resource. * According to the state of this font, the PDF File will be PDF/A conforming * file or not. (Ex : if the FontContainer flags this font as not embedded, * the PDF is a PDF/A only if the font is used in a Rendering Mode 3.) */ protected AbstractFontContainer fontContainer = null; /** * Abstract Constructor * @param handler the handled document * @param cObj The cos object representing the font * @throws ValidationException when object creation fails */ public AbstractFontValidator(DocumentHandler handler, COSObject cObj) throws ValidationException { try { this.handler = handler; this.cObj = cObj; this.fDictionary = (COSDictionary) cObj.getObject(); this.pFont = PDFontFactory.createFont(fDictionary); this.fontContainer = instanciateContainer(this.pFont); this.handler.addFont(this.pFont.getCOSObject(), this.fontContainer); } catch (IOException e) { throw new ValidationException( "Unable to instantiate a FontValidator object : " + e.getMessage()); } } protected AbstractFontContainer instanciateContainer (PDFont fd) { String subtype = fd.getSubType(); if (FONT_DICTIONARY_VALUE_TRUETYPE.equals(subtype)) { return new TrueTypeFontContainer(fd); } else if (FONT_DICTIONARY_VALUE_MMTYPE.equals(subtype)) { return new Type1FontContainer(fd); } else if (FONT_DICTIONARY_VALUE_TYPE1.equals(subtype)) { return new Type1FontContainer(fd); } else if (FONT_DICTIONARY_VALUE_TYPE3.equals(subtype)) { return new Type3FontContainer(fd); } else if (FONT_DICTIONARY_VALUE_COMPOSITE.equals(subtype)) { return new CompositeFontContainer(fd); } else { return new UndefFontContainer(fd); } } private static final String subSetPattern = "^[A-Z]{6}\\+.*"; public static boolean isSubSet(String fontName) { return fontName.matches(subSetPattern); } public static String getSubSetPatternDelimiter() { return "+"; } /* * (non-Javadoc) * * @see net.awl.edoc.pdfa.validation.font.FontValidator#getState() */ public State getState() { return this.fontContainer.isValid(); } /* * (non-Javadoc) * * @see net.awl.edoc.pdfa.validation.font.FontValidator#getValdiationErrors() */ public List<ValidationError> getValdiationErrors() { return this.fontContainer.getErrors(); } /** * Type0, Type1 and TrueType FontValidatir call this method to check the * FontFile meta data. * * @param fontDesc * The FontDescriptor which contains the FontFile stream * @param fontFile * The font file stream to check * @return true if the meta data is valid, false otherwise * @throws ValidationException when checking fails */ protected boolean checkFontFileMetaData(PDFontDescriptor fontDesc, PDStream fontFile) throws ValidationException { PDMetadata metadata = fontFile.getMetadata(); if (metadata != null) { // --- Filters are forbidden in a XMP stream if (metadata.getFilters() != null && !metadata.getFilters().isEmpty()) { fontContainer.addError(new ValidationError( ValidationConstants.ERROR_SYNTAX_STREAM_INVALID_FILTER, "Filter specified in font file metadata dictionnary")); return false; } // --- extract the meta data content byte[] mdAsBytes = null; try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); InputStream metaDataContent = metadata.createInputStream(); IOUtils.copyLarge(metaDataContent, bos); IOUtils.closeQuietly(metaDataContent); IOUtils.closeQuietly(bos); mdAsBytes = bos.toByteArray(); } catch (IOException e) { throw new ValidationException("Unable to read font metadata due to : " + e.getMessage(), e); } try { XMPDocumentBuilder xmpBuilder = new XMPDocumentBuilder(); XMPMetadata xmpMeta = xmpBuilder.parse(mdAsBytes); FontMetaDataValidation fontMDval = new FontMetaDataValidation(); List<ValidationError> ve = new ArrayList<ValidationError>(); boolean isVal = fontMDval.analyseFontName(xmpMeta, fontDesc, ve); isVal = isVal && fontMDval.analyseFontName(xmpMeta, fontDesc, ve); for (ValidationError validationError : ve) { fontContainer.addError(validationError); } return isVal; } catch (XmpUnknownValueTypeException e) { fontContainer.addError(new ValidationError( ValidationConstants.ERROR_METADATA_UNKNOWN_VALUETYPE, e .getMessage())); return false; } catch (XmpParsingException e) { fontContainer.addError(new ValidationError( ValidationConstants.ERROR_METADATA_FORMAT, e.getMessage())); return false; } catch (XmpSchemaException e) { fontContainer.addError(new ValidationError( ValidationConstants.ERROR_METADATA_FORMAT, e.getMessage())); return false; } catch (XmpExpectedRdfAboutAttribute e) { fontContainer.addError(new ValidationError(ValidationConstants.ERROR_METADATA_RDF_ABOUT_ATTRIBUTE_MISSING,e.getMessage())); return false; } catch (BadFieldValueException e) { fontContainer.addError(new ValidationError(ValidationConstants.ERROR_METADATA_CATEGORY_PROPERTY_INVALID,e.getMessage())); return false; } catch (XmpXpacketEndException e) { throw new ValidationException("Unable to parse font metadata due to : " + e.getMessage(), e); } } // --- No MetaData, valid return true; } }