/***************************************************************************** * * 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. * ****************************************************************************/ package org.apache.padaf.preflight.font; import java.io.ByteArrayInputStream; import java.io.IOException; import org.apache.commons.io.IOUtils; import org.apache.fontbox.ttf.CMAPEncodingEntry; import org.apache.fontbox.ttf.CMAPTable; import org.apache.fontbox.ttf.TTFParser; import org.apache.fontbox.ttf.TrueTypeFont; import org.apache.padaf.preflight.DocumentHandler; import org.apache.padaf.preflight.ValidationConstants; import org.apache.padaf.preflight.ValidationException; import org.apache.padaf.preflight.ValidationResult; import org.apache.padaf.preflight.ValidationResult.ValidationError; import org.apache.pdfbox.cos.COSDictionary; import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.cos.COSObject; import org.apache.pdfbox.cos.COSStream; import org.apache.pdfbox.encoding.Encoding; import org.apache.pdfbox.encoding.MacRomanEncoding; import org.apache.pdfbox.encoding.WinAnsiEncoding; import org.apache.pdfbox.pdmodel.common.PDStream; public class TrueTypeFontValidator extends SimpleFontValidator { /** * @param handler * @param obj */ public TrueTypeFontValidator(DocumentHandler handler, COSObject obj) throws ValidationException { super(handler, obj); } /** * Check if mandatory fields are present. Return false if a field is missing, * true otherwise. If validation fails, the FontContainer is updated. */ protected boolean checkSpecificMandatoryFields() { // ---- name is required only in a PDF-1.0. // ---- Currently our grammar matches only with PDF-1.[1-4] // ---- BaseFont is required and is usually the FontName if (basefont == null || "".equals(basefont)) { this.fontContainer.addError(new ValidationError( ERROR_FONTS_DICTIONARY_INVALID, "BaseFont is missing")); // continue to process this font dictionary is useless return false; } boolean allPresent = (firstChar != null && lastChar != null && widths != null); if (!allPresent) { this.fontContainer.addError(new ValidationError( ERROR_FONTS_DICTIONARY_INVALID, "Required keys are missing")); return false; } // else ok return true; } /* * (non-Javadoc) * * @see * net.awl.edoc.pdfa.validation.font.SimpleFontValidator#checkFontDescriptor() */ @Override protected boolean checkFontDescriptor() throws ValidationException { boolean res = checkFontDescriptorMandatoryFields(); res = res && checkFontName(); res = res && checkFontFileElement(); return res; } /** * If the FontName is missing from the FontDescriptor dictionary, this method * returns false and the FontContainer is updated. * * @return */ protected boolean checkFontName() { String fontName = this.pFontDesc.getFontName(); if (fontName == null) { this.fontContainer.addError(new ValidationResult.ValidationError( ValidationConstants.ERROR_FONTS_DESCRIPTOR_INVALID, "The FontName in font descriptor is null")); return false; } return true; } /** * This methods validates the Font Stream. If the font is damaged or missing * the FontContainer is updated and false is returned. Moreover, this method * checks the Encoding property of the FontDescriptor dictionary. * * @return */ protected boolean checkFontFileElement() throws ValidationException { PDStream ff1 = pFontDesc.getFontFile(); PDStream ff2 = pFontDesc.getFontFile2(); PDStream ff3 = pFontDesc.getFontFile3(); boolean onlyOne = (ff1 != null && ff2 == null && ff3 == null) || (ff1 == null && ff2 != null && ff3 == null) || (ff1 == null && ff2 == null && ff3 != null); if (ff2 == null || !onlyOne) { this.fontContainer.addError(new ValidationResult.ValidationError( ValidationConstants.ERROR_FONTS_FONT_FILEX_INVALID, "The FontFile2 is invalid")); return false; } // ---- Stream validation should be done by the StreamValidateHelper. // ---- Process font specific check COSStream stream = ff2.getStream(); if (stream == null) { this.fontContainer.addError(new ValidationResult.ValidationError( ValidationConstants.ERROR_FONTS_FONT_FILEX_INVALID, "The FontFile is missing")); this.fontContainer.setFontProgramEmbedded(false); return false; } boolean hasLength1 = stream.getInt(COSName .getPDFName(FONT_DICTIONARY_KEY_LENGTH1)) > 0; if (!hasLength1) { this.fontContainer.addError(new ValidationResult.ValidationError( ValidationConstants.ERROR_FONTS_FONT_FILEX_INVALID, "The FontFile is invalid")); return false; } // ---- check the encoding part. if (pFontDesc.isNonSymbolic()) { // ---- only MacRomanEncoding or WinAnsiEncoding are allowed for a non // symbolic font Encoding encodingValue = this.pFont.getFontEncoding(); if (encodingValue == null || !(encodingValue instanceof MacRomanEncoding || encodingValue instanceof WinAnsiEncoding)) { this.fontContainer.addError(new ValidationResult.ValidationError( ValidationConstants.ERROR_FONTS_ENCODING, "The Encoding is invalid for the NonSymbolic TTF")); return false; } } else if (pFontDesc.isSymbolic()) { // ---- For symbolic font, no encoding entry is allowed and only one // encoding entry is expected into the FontFile CMap if (((COSDictionary) this.fDictionary.getCOSObject()).getItem(COSName .getPDFName(FONT_DICTIONARY_KEY_ENCODING)) != null) { this.fontContainer.addError(new ValidationResult.ValidationError( ValidationConstants.ERROR_FONTS_ENCODING, "The Encoding should be missing for the Symbolic TTF")); return false; } // else check the content of the Font CMap (see below) } else { // ----- should never happen return true; } /* * ---- try to load the font using the TTFParser object. If the font is * invalid, an exception will be thrown. Because of it is a Embedded Font * Program, some tables are required and other are optional see PDF * Reference (ยง5.8) */ ByteArrayInputStream bis = null; try { bis = new ByteArrayInputStream(ff2.getByteArray()); TrueTypeFont ttf = new TTFParser(true).parseTTF(bis); if (pFontDesc.isSymbolic() && ttf.getCMAP().getCmaps().length != 1) { this.fontContainer.addError(new ValidationResult.ValidationError( ValidationConstants.ERROR_FONTS_ENCODING, "The Encoding should be missing for the Symbolic TTF")); return false; } ((TrueTypeFontContainer)this.fontContainer).setFontObjectAndInitializeInnerFields(ttf); ((TrueTypeFontContainer)this.fontContainer).setCMap(getCMapOfFontProgram(ttf)); return checkFontFileMetaData(pFontDesc, ff2); } catch (IOException e) { this.fontContainer.addError(new ValidationResult.ValidationError( ValidationConstants.ERROR_FONTS_TRUETYPE_DAMAGED, "The FontFile can't be read")); return false; } finally { if (bis != null) { IOUtils.closeQuietly(bis); } } } /** * Return the CMap encoding entry to use. This CMap belong to the TrueType * Font Program. * * Here the selection rules : * <UL> * <li>For a Symbolic TrueType, the Font Program has only one CMap (Checked in * the checkFontFileElement method) * <li>For a Non-Symbolic TrueType, only two CMap can be used (WinAnsi * (plateformId : 3 / encodingId : 1) or MacRoman (plateformId : 1 / * encodingId : 0) ). This CMap returns the CMap which corresponds to the * Encoding value of the FontDescriptor dictionary. * </UL> * * @param ttf * The FontBox object which manages a TrueType Font program. * @return * @throws ValidationException * if the FontProgram doesn't have the expected CMap */ protected CMAPEncodingEntry getCMapOfFontProgram(TrueTypeFont ttf) throws ValidationException { CMAPTable cmap = ttf.getCMAP(); if (this.pFontDesc.isSymbolic()) { return cmap.getCmaps()[0]; } else { if (this.pFont.getFontEncoding() instanceof WinAnsiEncoding) { for (CMAPEncodingEntry cmapEntry : cmap.getCmaps()) { // ---- Returns the WinAnsiEncoding CMap if ((cmapEntry.getPlatformId() == 3) && (cmapEntry.getPlatformEncodingId() == 1)) { return cmapEntry; } } } else { // ---- Returns the MacRomanEncoding CMap for (CMAPEncodingEntry cmapEntry : cmap.getCmaps()) { if ((cmapEntry.getPlatformId() == 1) && (cmapEntry.getPlatformEncodingId() == 0)) { return cmapEntry; } } } } throw new ValidationException("CMap not found in the TrueType FontProgam"); } }