/*******************************************************************************
* 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.ByteArrayInputStream;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.List;
import net.padaf.preflight.DocumentHandler;
import net.padaf.preflight.ValidationConstants;
import net.padaf.preflight.ValidationException;
import net.padaf.preflight.ValidationResult;
import net.padaf.preflight.ValidationResult.ValidationError;
import net.padaf.preflight.utils.COSUtils;
import org.apache.fontbox.cff.CFFFont;
import org.apache.fontbox.cff.CFFParser;
import org.apache.fontbox.cmap.CMap;
import org.apache.fontbox.cmap.CMapParser;
import org.apache.fontbox.ttf.CIDFontType2Parser;
import org.apache.fontbox.ttf.TrueTypeFont;
import org.apache.pdfbox.cos.COSArray;
import org.apache.pdfbox.cos.COSBase;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSDocument;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.cos.COSObject;
import org.apache.pdfbox.cos.COSStream;
import org.apache.pdfbox.pdmodel.common.PDStream;
import org.apache.pdfbox.pdmodel.font.PDFontDescriptorDictionary;
public class CompositeFontValidator extends AbstractFontValidator {
protected String basefont;
protected COSBase descendantFonts;
protected COSDictionary cidFont;
protected COSBase encoding;
protected COSStream cmap;
protected COSBase toUnicode;
protected CMap cidToGidMap = null;
protected boolean isIdentityCMap = false;
public CompositeFontValidator(DocumentHandler handler, COSObject obj)
throws ValidationException {
super(handler, obj);
}
/**
* This methods extracts from the Font dictionary all mandatory fields. If a
* mandatory field is missing, the list of ValidationError in the
* FontContainer is updated. On error, the method returns false.
*
* @return
*/
protected boolean checkMandatoryFields() {
String type = fDictionary.getNameAsString(COSName
.getPDFName(DICTIONARY_KEY_TYPE));
String subtype = fDictionary.getNameAsString(COSName
.getPDFName(DICTIONARY_KEY_SUBTYPE));
// ---- just check if they are present because of the Helper has already
// checked them.
if ((type == null || "".equals(type))
|| (subtype == null || "".equals(subtype))) {
this.fontContainer.addError(new ValidationError(
ERROR_FONTS_DICTIONARY_INVALID,
"Type and/or Subtype keys are missing"));
return false;
}
// ---- Check presence of baseFont, CMap and CIDFont
this.basefont = fDictionary.getNameAsString(COSName
.getPDFName(FONT_DICTIONARY_KEY_BASEFONT));
this.descendantFonts = fDictionary.getItem(COSName
.getPDFName(FONT_DICTIONARY_KEY_DESCENDANT_FONTS));
this.encoding = fDictionary.getItem(COSName
.getPDFName(FONT_DICTIONARY_KEY_ENCODING));
if ((basefont == null || "".equals(basefont)) || descendantFonts == null
|| encoding == null) {
// ---- baseFont syntax isn't checked because of it is a convention not a
// rule
this.fontContainer.addError(new ValidationError(
ERROR_FONTS_DICTIONARY_INVALID,
"BaseFont, Encoding or DescendantFonts keys are missing"));
return false;
}
// ---- toUnicode is optional, but keep the value if present.
this.toUnicode = fDictionary.getItem(COSName
.getPDFName(FONT_DICTIONARY_KEY_TOUNICODE));
return true;
}
/**
* This method validates the CIDFont dictionary.
*
* This method returns false and updates the list of errors in the
* FontContainer if some mandatory fields are missing.
*
* This method calls the processCIDFontTypeX method to check if the font is
* damaged or not. If the font is damaged, the errors list is updated and the
* method return false.
*
* @return
* @throws ValidationException
*/
protected boolean checkCIDFont() throws ValidationException {
// ---- a CIDFont is contained in the DescendantFonts array
COSDocument cDoc = this.handler.getDocument().getDocument();
COSArray array = COSUtils.getAsArray(descendantFonts, cDoc);
if (array == null) {
this.fontContainer.addError(new ValidationError(
ERROR_FONTS_CIDKEYED_INVALID,
"CIDFont is missing from the DescendantFonts array"));
return false;
}
// ---- in PDF 1.4, this array must contain only one element,
// because of a PDF/A should be a PDF 1.4, this method returns an error if
// the array
// has more than one element.
if (array.size() != 1) {
this.fontContainer.addError(new ValidationError(
ERROR_FONTS_CIDKEYED_INVALID,
"The DescendantFonts array should have one element."));
return false;
}
this.cidFont = COSUtils.getAsDictionary(array.get(0), cDoc);
if (this.cidFont == null) {
this.fontContainer
.addError(new ValidationError(ERROR_FONTS_CIDKEYED_INVALID,
"The DescendantFonts array should have one element with is a dictionary."));
return false;
}
String type = cidFont.getNameAsString(COSName
.getPDFName(DICTIONARY_KEY_TYPE));
String subtype = cidFont.getNameAsString(COSName
.getPDFName(DICTIONARY_KEY_SUBTYPE));
if ((type == null || "".equals(type))
|| (subtype == null || "".equals(subtype))) {
this.fontContainer.addError(new ValidationError(
ERROR_FONTS_DICTIONARY_INVALID,
"Type and/or Subtype keys are missing"));
return false;
}
boolean isT0 = FONT_DICTIONARY_VALUE_TYPE0.equals(subtype);
boolean isT2 = FONT_DICTIONARY_VALUE_TYPE2.equals(subtype);
// ---- Even if these entries are present, values must be checked.
if (!FONT_DICTIONARY_VALUE_FONT.equals(type) || !(isT0 || isT2)) {
this.fontContainer.addError(new ValidationError(
ERROR_FONTS_DICTIONARY_INVALID,
"Type and/or Subtype keys are missing"));
return false;
}
// ---- BaseFont is mandatory
String bf = cidFont.getNameAsString(COSName
.getPDFName(FONT_DICTIONARY_KEY_BASEFONT));
if (bf == null || "".equals(bf)) {
this.fontContainer.addError(new ValidationError(
ERROR_FONTS_DICTIONARY_INVALID, "BaseFont is missing"));
return false;
}
// ---- checks others mandatory fields
COSBase sysinfo = cidFont.getItem(COSName
.getPDFName(FONT_DICTIONARY_KEY_CID_SYSINFO));
COSBase fontDesc = cidFont.getItem(COSName
.getPDFName(FONT_DICTIONARY_KEY_FONT_DESC));
COSBase cidToGid = cidFont.getItem(COSName
.getPDFName(FONT_DICTIONARY_KEY_CID_GIDMAP));
boolean result = checkCIDSystemInfo(sysinfo, cDoc);
if (isT0) {
result = result && checkCIDToGIDMap(cidToGid, cDoc, false);
result = result && processCIDFontType0(fontDesc);
} else {
result = result && checkCIDToGIDMap(cidToGid, cDoc, true);
result = result && processCIDFontType2(fontDesc);
}
return result;
}
/**
* Check the content of the CIDSystemInfo dictionary. A CIDSystemInfo
* dictionary must contain :
* <UL>
* <li>a Name - Registry
* <li>a Name - Ordering
* <li>a Integer - Supplement
* </UL>
*
* @param sysinfo
* @param cDoc
* @return
*/
private boolean checkCIDSystemInfo(COSBase sysinfo, COSDocument cDoc) {
COSDictionary cidSysInfo = COSUtils.getAsDictionary(sysinfo, cDoc);
if (cidSysInfo == null) {
this.fontContainer.addError(new ValidationError(
ERROR_FONTS_CIDKEYED_SYSINFO));
return false;
}
COSBase reg = cidSysInfo.getItem(COSName
.getPDFName(FONT_DICTIONARY_KEY_SYSINFO_REGISTRY));
COSBase ord = cidSysInfo.getItem(COSName
.getPDFName(FONT_DICTIONARY_KEY_SYSINFO_ORDERING));
COSBase sup = cidSysInfo.getItem(COSName
.getPDFName(FONT_DICTIONARY_KEY_SYSINFO_SUPPLEMENT));
if (!(COSUtils.isString(reg, cDoc) && COSUtils.isString(ord, cDoc) && COSUtils
.isInteger(sup, cDoc))) {
this.fontContainer.addError(new ValidationError(
ERROR_FONTS_CIDKEYED_SYSINFO));
return false;
}
return true;
}
/**
* This method checks the CIDtoGIDMap entry of the Font dictionary. This
* element must be a Stream or a Name. If it is a name, it must be "Identity"
* otherwise, the PDF file isn't a PDF/A-1b.
*
* If the validation fails, the method returns false and the list of error in
* the FontContainer is updated.
*
* If the CIDtoGIDMap is a Stream, it is parsed as a CMap and the result is
* kept in the cidToGidMap attribute.
*
* @param ctog
* @param cDoc
* @param mandatory
* @return
*/
private boolean checkCIDToGIDMap(COSBase ctog, COSDocument cDoc,
boolean mandatory) {
if (COSUtils.isString(ctog, cDoc)) {
// ---- valid only if the string is Identity
String ctogStr = COSUtils.getAsString(ctog, cDoc);
if (!FONT_DICTIONARY_VALUE_CMAP_IDENTITY.equals(ctogStr)) {
this.fontContainer.addError(new ValidationError(
ERROR_FONTS_CIDKEYED_CIDTOGID,"The CIDToGID entry is invalid"));
return false;
}
} else if (COSUtils.isStream(ctog, cDoc)) {
try {
COSStream ctogMap = COSUtils.getAsStream(ctog, cDoc);
this.cidToGidMap = new CMapParser().parse(null, ctogMap
.getUnfilteredStream());
} catch (IOException e) {
// ---- map can be invalid, return a Validation Error
this.fontContainer.addError(new ValidationError(
ERROR_FONTS_CIDKEYED_CIDTOGID));
return false;
}
} else if (mandatory) {
this.fontContainer.addError(new ValidationError(
ERROR_FONTS_CIDKEYED_CIDTOGID));
return false;
}
return true;
}
/**
* Check the CMap entry.
*
* The CMap entry must be a dictionary in a PDF/A. This entry can be a String
* only if the String value is Identity-H or Identity-V
*
* @param errors
* @return
*/
protected boolean checkCMap(COSBase aEncoding) {
COSDocument cDoc = this.handler.getDocument().getDocument();
if (COSUtils.isString(aEncoding, cDoc)) {
// ---- if encoding is a string, only 2 values are allowed
String str = COSUtils.getAsString(aEncoding, cDoc);
if (!(FONT_DICTIONARY_VALUE_CMAP_IDENTITY_V.equals(str) || FONT_DICTIONARY_VALUE_CMAP_IDENTITY_H
.equals(str))) {
this.fontContainer.addError(new ValidationError(
ERROR_FONTS_CIDKEYED_INVALID,
"The CMap is a string but it isn't an Identity-H/V"));
return false;
}
isIdentityCMap = true;
} else if (COSUtils.isStream(aEncoding, cDoc)) {
// ---- If the CMap is a stream, some fields are mandatory
// and the CIDSytemInfo must be compared with the CIDSystemInfo
// entry of the CIDFont.
return processCMapAsStream(COSUtils.getAsStream(aEncoding, cDoc));
} else {
// ---- CMap type is invalid
this.fontContainer.addError(new ValidationError(
ERROR_FONTS_CIDKEYED_CMAP_INVALID_OR_MISSING,
"The CMap type is invalid"));
return false;
}
return true;
}
/**
* Standard information of a stream element will be checked by the
* StreamHelper.
*
* This method checks mandatory fields of the CMap stream. This method checks
* too if the CMap stream is damaged using the CMapParser of the fontbox api.
*
* @param aCMap
* @return
*/
private boolean processCMapAsStream(COSStream aCMap) {
COSDocument cDoc = handler.getDocument().getDocument();
String type = aCMap
.getNameAsString(COSName.getPDFName(DICTIONARY_KEY_TYPE));
String cmapName = aCMap.getNameAsString(COSName
.getPDFName(FONT_DICTIONARY_KEY_CMAP_NAME));
COSBase sysinfo = aCMap.getItem(COSName
.getPDFName(FONT_DICTIONARY_KEY_CID_SYSINFO));
int wmode = aCMap
.getInt(COSName.getPDFName(FONT_DICTIONARY_KEY_CMAP_WMODE));
COSBase cmapUsed = aCMap.getItem(COSName
.getPDFName(FONT_DICTIONARY_KEY_CMAP_USECMAP));
if (!FONT_DICTIONARY_VALUE_TYPE_CMAP.equals(type)) {
// ---- CMap type is invalid
this.fontContainer.addError(new ValidationError(
ERROR_FONTS_CIDKEYED_CMAP_INVALID_OR_MISSING,
"The CMap type is invalid"));
return false;
}
// ---- check the content of the CIDSystemInfo
if (!checkCIDSystemInfo(sysinfo, cDoc)) {
return false;
}
if (cmapName == null || "".equals(cmapName) || wmode > 1) {
this.fontContainer.addError(new ValidationError(
ERROR_FONTS_CIDKEYED_CMAP_INVALID_OR_MISSING,
"Some elements in the CMap dictionary are missing or invalid"));
return false;
}
try {
CMap fontboxCMap = new CMapParser().parse(null, aCMap
.getUnfilteredStream());
int wmValue = fontboxCMap.getWMode();
String cmnValue = fontboxCMap.getName(); //getCmapEntry("CMapName");
if (wmValue != wmode) {
this.fontContainer.addError(new ValidationError(
ERROR_FONTS_CIDKEYED_CMAP_INVALID_OR_MISSING,
"WMode is inconsistent"));
return false;
}
if (!cmnValue.equals(cmapName)) {
this.fontContainer.addError(new ValidationError(
ERROR_FONTS_CIDKEYED_CMAP_INVALID_OR_MISSING,
"CMapName is inconsistent"));
return false;
}
} catch (IOException e) {
this.fontContainer.addError(new ValidationError(
ERROR_FONTS_CID_CMAP_DAMAGED, "The CMap type is damaged"));
return false;
}
if (cmapUsed != null) {
return checkCMap(cmapUsed);
}
return true;
}
/**
* The CIDSystemInfo must have the same Registry and Ordering for CMap and
* CIDFont. This control is useless if CMap is Identity-H or Identity-V so
* this method is called by the checkCMap method.
*
* @param errors
* @return
*/
private boolean compareCIDSystemInfo() {
COSDocument cDoc = handler.getDocument().getDocument();
if (!isIdentityCMap) {
COSDictionary cmsi = COSUtils.getAsDictionary(this.cmap.getItem(COSName
.getPDFName(FONT_DICTIONARY_KEY_CID_SYSINFO)), cDoc);
COSDictionary cfsi = COSUtils.getAsDictionary(this.cidFont
.getItem(COSName.getPDFName(FONT_DICTIONARY_KEY_CID_SYSINFO)), cDoc);
String regCM = COSUtils.getAsString(cmsi.getItem(COSName
.getPDFName(FONT_DICTIONARY_KEY_SYSINFO_REGISTRY)), cDoc);
String ordCM = COSUtils.getAsString(cmsi.getItem(COSName
.getPDFName(FONT_DICTIONARY_KEY_SYSINFO_ORDERING)), cDoc);
String regCF = COSUtils.getAsString(cfsi.getItem(COSName
.getPDFName(FONT_DICTIONARY_KEY_SYSINFO_REGISTRY)), cDoc);
String ordCF = COSUtils.getAsString(cfsi.getItem(COSName
.getPDFName(FONT_DICTIONARY_KEY_SYSINFO_ORDERING)), cDoc);
if (!regCF.equals(regCM) || !ordCF.equals(ordCM)) {
this.fontContainer.addError(new ValidationError(
ERROR_FONTS_CIDKEYED_SYSINFO, "The CIDSystemInfo is inconsistent"));
return false;
}
} // else cmap is null because it is a Identity-H/V
return true;
}
/*
* (non-Javadoc)
*
* @see
* net.awl.edoc.pdfa.validation.font.FontValidator#validate(java.util.List)
*/
public boolean validate() throws ValidationException {
boolean result = checkMandatoryFields();
result = result && checkCIDFont();
result = result && checkCMap(encoding);
if (result) {
this.cmap = COSUtils.getAsStream(encoding, handler.getDocument()
.getDocument());
}
result = result && compareCIDSystemInfo();
return result;
}
/**
* Check if all required fields are present in the PDF file to describe the
* Font Descriptor.
*
* @param handler
* @param fontDescriptor
* @param result
*/
protected boolean checkFontDescriptorMandatoryFields(
PDFontDescriptorDictionary pdFontDesc) {
boolean fname = false, flags = false, itangle = false, cheight = false;
boolean fbbox = false, asc = false, desc = false, stemv = false;
COSDictionary fontDescDictionary = pdFontDesc.getCOSDictionary();
for (Object key : fontDescDictionary.keySet()) {
if (!(key instanceof COSName)) {
this.fontContainer.addError(new ValidationResult.ValidationError(
ValidationConstants.ERROR_SYNTAX_DICTIONARY_KEY_INVALID,
"Invalid key in The font descriptor"));
return false;
}
String cosName = ((COSName) key).getName();
if (cosName.equals(FONT_DICTIONARY_KEY_FONTNAME)) {
fname = true;
}
if (cosName.equals(FONT_DICTIONARY_KEY_FLAGS)) {
flags = true;
}
if (cosName.equals(FONT_DICTIONARY_KEY_ITALICANGLE)) {
itangle = true;
}
if (cosName.equals(FONT_DICTIONARY_KEY_CAPHEIGHT)) {
cheight = true;
}
if (cosName.equals(FONT_DICTIONARY_KEY_FONTBBOX)) {
fbbox = true;
}
if (cosName.equals(FONT_DICTIONARY_KEY_ASCENT)) {
asc = true;
}
if (cosName.equals(FONT_DICTIONARY_KEY_DESCENT)) {
desc = true;
}
if (cosName.equals(FONT_DICTIONARY_KEY_STEMV)) {
stemv = true;
}
}
if (!(fname && flags && itangle && cheight && fbbox && asc && desc && stemv)) {
this.fontContainer.addError(new ValidationError(
ERROR_FONTS_DESCRIPTOR_INVALID, "Some mandatory fields are missing"));
return false;
}
return true;
}
/**
* Process the CIDFontType0 validation.
*
* @param fontDesc
* The FontDescriptor which contains the Font Program
* @return
* @throws ValidationException
*/
protected boolean processCIDFontType0(COSBase fontDesc)
throws ValidationException {
COSDictionary fontDescDic = COSUtils.getAsDictionary(fontDesc, handler
.getDocument().getDocument());
if (fontDescDic == null) {
throw new ValidationException(
"Unable to process CIDFontType0 because of the font descriptor is invalid.");
}
PDFontDescriptorDictionary pfDescriptor = new PDFontDescriptorDictionary(
fontDescDic);
boolean isValid = checkFontDescriptorMandatoryFields(pfDescriptor);
isValid = isValid && checkCIDKeyedFontName(pfDescriptor, true);
isValid = isValid && checkFontFileElement_CIDFontType0(pfDescriptor);
isValid = isValid && checkCIDSet(pfDescriptor);
return isValid;
}
/**
* Checks if the FontName contained in the FontDescriptor dictionary of the
* CID-Keyed Font is present. (The FontName is mandatory according to the PDF
* Reference.) If the consistency must be checked, the FontName contained in
* the FontDescriptor is consistent with the BaseName of the font dictionary.
* If font name is invalid, the list of validation errors in the FontContainer
* is updated.
*
* @param pfDescriptor
* The FontDescriptor dictionary which contains the FontName to check
* @param checkConsistency
* true if the font name must be consistent with the BaseName of the
* Font dictionary
* @return
*/
protected boolean checkCIDKeyedFontName(
PDFontDescriptorDictionary pfDescriptor, boolean checkConsistency) {
String fontName = pfDescriptor.getFontName();
String baseName = this.pFont.getBaseFont();
if (fontName == null) {
this.fontContainer.addError(new ValidationResult.ValidationError(
ERROR_FONTS_DESCRIPTOR_INVALID,
"The FontName in font descriptor is missing"));
return false;
}
if (checkConsistency
&& !(fontName.equals(baseName) || fontName.contains(baseName) || baseName
.contains(fontName))) {
this.fontContainer
.addError(new ValidationResult.ValidationError(
ERROR_FONTS_DESCRIPTOR_INVALID,
"The FontName in font descriptor isn't the same as the BaseFont in the Font dictionary"));
return false;
}
return true;
}
/**
* This method return false and updates the FontContainer if the Composite
* Font TYPE 0 or TYPE1C is damaged. This method checks the Widths
* consistency.
*
* @param pfDescriptor
* @return
*/
boolean checkFontFileElement_CIDFontType0(
PDFontDescriptorDictionary pfDescriptor) throws ValidationException {
// ---- FontFile Validation
PDStream ff1 = pfDescriptor.getFontFile();
PDStream ff2 = pfDescriptor.getFontFile2();
PDStream ff3 = pfDescriptor.getFontFile3();
boolean onlyOne = (ff1 != null && ff2 == null && ff3 == null)
|| (ff1 == null && ff2 != null && ff3 == null)
|| (ff1 == null && ff2 == null && ff3 != null);
if ((ff3 == null) || !onlyOne) {
this.fontContainer.addError(new ValidationResult.ValidationError(
ERROR_FONTS_FONT_FILEX_INVALID, "The FontFile is invalid"));
return false;
}
// ---- Stream validation should be done by the StreamValidateHelper.
// ---- Process font specific check
COSStream stream = ff3.getStream();
if (stream == null) {
this.fontContainer.addError(new ValidationResult.ValidationError(
ERROR_FONTS_FONT_FILEX_INVALID, "The FontFile is missing"));
this.fontContainer.setFontProgramEmbedded(false);
return false;
}
// ---- Lengthx aren't mandatory for this type of font
// ---- But the Subtype is a mandatory field with specific values
String st = stream.getNameAsString(COSName
.getPDFName(DICTIONARY_KEY_SUBTYPE));
if (!(FONT_DICTIONARY_VALUE_TYPE0C.equals(st) || FONT_DICTIONARY_VALUE_TYPE1C
.equals(st))) {
this.fontContainer.addError(new ValidationResult.ValidationError(
ERROR_FONTS_FONT_FILEX_INVALID,
"The FontFile3 stream doesn't have the right Subtype"));
return false;
}
// ---- try to load the font using the java.awt.font object.
// ---- if the font is invalid, an exception will be thrown
try {
CFFParser cffParser = new CFFParser();
List<CFFFont> lCFonts = cffParser.parse(ff3.getByteArray());
if (lCFonts == null || lCFonts.isEmpty()) {
this.fontContainer.addError(new ValidationResult.ValidationError(
ERROR_FONTS_CID_DAMAGED, "The FontFile can't be read"));
return false;
}
return checkCIDFontWidths(lCFonts)
&& checkFontFileMetaData(pfDescriptor, ff3);
} catch (IOException e) {
this.fontContainer.addError(new ValidationResult.ValidationError(
ERROR_FONTS_CID_DAMAGED, "The FontFile can't be read"));
return false;
}
}
/**
* This method checks the metric consistency of a CIDFontType2.
*
* @param ttf
* The TrueTypeFont object which represent the CIDFontType2 Font
* Program.
* @return
* @throws ValidationException
*/
protected boolean checkTTFontMetrics(TrueTypeFont ttf)
throws ValidationException {
LinkedHashMap<Integer, Integer> widths = getWidthsArray();
int defaultWidth = this.cidFont.getInt("DW", 1000);
int unitsPerEm = ttf.getHeader().getUnitsPerEm();
int[] glyphWidths = ttf.getHorizontalMetrics().getAdvanceWidth();
/* In a Mono space font program, the length of the AdvanceWidth array must be one.
* According to the TrueType font specification, the Last Value of the AdvanceWidth array
* is apply to the subsequent glyphs. So if the GlyphId is greater than the length of the array
* the last entry is used.
*/
int numberOfLongHorMetrics = ttf.getHorizontalHeader().getNumberOfHMetrics();
CFFType2FontContainer type2FontContainer = ((CompositeFontContainer)this.fontContainer).getCFFType2();
type2FontContainer.setPdfWidths(widths);
type2FontContainer.setCmap(this.cidToGidMap);
type2FontContainer.setDefaultGlyphWidth(defaultWidth);
type2FontContainer.setFontObject(ttf);
type2FontContainer.setGlyphWidths(glyphWidths);
type2FontContainer.setNumberOfLongHorMetrics(numberOfLongHorMetrics);
type2FontContainer.setUnitsPerEm(unitsPerEm);
return true;
}
/**
* This method check Metrics consistency of the CIDFontType0.
*
* @param lCFonts
* @return
* @throws ValidationException
*/
protected boolean checkCIDFontWidths(List<CFFFont> lCFonts)
throws ValidationException {
// ---- Extract Widths and default Width from the CIDFont dictionary
LinkedHashMap<Integer, Integer> widths = getWidthsArray();
int defaultWidth = this.cidFont.getInt("DW", 1000);
CFFType0FontContainer type0FontContainer = ((CompositeFontContainer)this.fontContainer).getCFFType0();
type0FontContainer.setFontObject(lCFonts);
type0FontContainer.setDefaultGlyphWidth(defaultWidth);
type0FontContainer.setWidthsArray(widths);
return true;
}
/**
* This method return false and updates the FontContainer if the Composite
* Font TYPE 2 is damaged or missing.
*
* @param pfDescriptor
* @return
*/
boolean checkFontFileElement_CIDFontType2(
PDFontDescriptorDictionary pfDescriptor) throws ValidationException {
// ---- FontFile Validation
PDStream ff1 = pfDescriptor.getFontFile();
PDStream ff2 = pfDescriptor.getFontFile2();
PDStream ff3 = pfDescriptor.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(
ERROR_FONTS_FONT_FILEX_INVALID, "The FontFile 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(
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;
}
// ---- try to load the font using the java.awt.font object.
// ---- if the font is invalid, an exception will be thrown
TrueTypeFont ttf = null;
try {
// ---- According to PDF Reference, CIDFontType2 is a TrueType font.
// ---- Remark : Java.awt.Font throws exception when a CIDFontType2 is
// parsed even if it is valid.
ttf = new CIDFontType2Parser(true).parseTTF(new ByteArrayInputStream(ff2
.getByteArray()));
} catch (Exception e) {
// ---- Exceptionally, Exception is catched Here because of damaged font
// can throw NullPointer Exception...
this.fontContainer.addError(new ValidationResult.ValidationError(
ERROR_FONTS_CID_DAMAGED, "The FontFile can't be read"));
return false;
}
return checkTTFontMetrics(ttf) && checkFontFileMetaData(pfDescriptor, ff2);
}
/**
* For a CIDFont the width array, there are two formats of width array :
* <UL>
* <li>C [W1...Wn] : C is an integer specifying a starting CID value and the
* array of n numbers specify widths for n consecutive CIDs.
* <li>Cf Cl W : Defines the same width W for the range Cf to Cl
* </UL>
* This method gets a linked hash map of width where the key is a CID and the
* value is the Width.
*
* @return
* @throws ValidationException
*/
protected LinkedHashMap<Integer, Integer> getWidthsArray()
throws ValidationException {
LinkedHashMap<Integer, Integer> widthsMap = new LinkedHashMap<Integer, Integer>();
COSDocument cDoc = handler.getDocument().getDocument();
COSBase cBase = this.cidFont.getItem(COSName.getPDFName("W"));
COSArray wArr = COSUtils.getAsArray(cBase, cDoc);
for (int i = 0; i < wArr.size();) {
int firstCid = wArr.getInt(i);
if (i + 1 >= wArr.size()) {
throw new ValidationException("Invalid format of the W entry");
}
COSBase cb = wArr.getObject(i + 1);
if (COSUtils.isArray(cb, cDoc)) {
// ---- First Format
COSArray seqWidths = COSUtils.getAsArray(cb, cDoc);
widthsMap.put(firstCid, seqWidths.getInt(0));
for (int jw = 1; jw < seqWidths.size(); jw++) {
widthsMap.put((firstCid + jw), seqWidths.getInt(jw));
}
i = i + 2;
} else {
// ---- Second Format
if (i + 2 >= wArr.size()) {
throw new ValidationException("Invalid format of the W entry");
}
int lastCid = wArr.getInt(i + 1);
int commonWidth = wArr.getInt(i + 2);
for (int jw = firstCid; jw <= lastCid; ++jw) {
widthsMap.put((firstCid + jw), commonWidth);
}
i = i + 3;
}
}
return widthsMap;
}
/**
* If the embedded font is a subset, the CIDSet entry is mandatory and must be
* a Stream. This method returns true if the CIDSet entry respects conditions,
* otherwise the method returns false and the FontContainer is updated.
*
* @param pfDescriptor
* @return
*/
protected boolean checkCIDSet(PDFontDescriptorDictionary pfDescriptor) {
if (isSubSet(pfDescriptor.getFontName())) {
COSBase cidset = pfDescriptor.getCOSDictionary().getItem(
COSName.getPDFName(FONT_DICTIONARY_KEY_CIDSET));
if (cidset == null
|| !COSUtils.isStream(cidset, this.handler.getDocument()
.getDocument())) {
this.fontContainer.addError(new ValidationResult.ValidationError(
ERROR_FONTS_CIDSET_MISSING_FOR_SUBSET,
"The CIDSet entry is missing for the Composite Subset"));
return false;
}
}
return true;
}
/**
*
* @param fontDesc
* @return
* @throws ValidationException
*/
protected boolean processCIDFontType2(COSBase fontDesc)
throws ValidationException {
COSDictionary fontDescDic = COSUtils.getAsDictionary(fontDesc, handler
.getDocument().getDocument());
if (fontDescDic == null) {
throw new ValidationException(
"Unable to process CIDFontType2 because of the font descriptor is invalid.");
}
PDFontDescriptorDictionary pfDescriptor = new PDFontDescriptorDictionary(
fontDescDic);
boolean isValid = checkFontDescriptorMandatoryFields(pfDescriptor);
isValid = isValid && checkCIDKeyedFontName(pfDescriptor, false);
isValid = isValid && checkFontFileElement_CIDFontType2(pfDescriptor);
isValid = isValid && checkCIDSet(pfDescriptor);
return isValid;
}
}