Package org.apache.padaf.preflight.font

Source Code of org.apache.padaf.preflight.font.Type3FontValidator

/*****************************************************************************
*
* 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.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;


import org.apache.padaf.preflight.DocumentHandler;
import org.apache.padaf.preflight.ValidationException;
import org.apache.padaf.preflight.ValidationResult.ValidationError;
import org.apache.padaf.preflight.contentstream.ContentStreamException;
import org.apache.padaf.preflight.font.AbstractFontContainer.State;
import org.apache.padaf.preflight.graphics.ExtGStateContainer;
import org.apache.padaf.preflight.graphics.ShadingPattern;
import org.apache.padaf.preflight.utils.COSUtils;
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.COSInteger;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.cos.COSObject;
import org.apache.pdfbox.cos.COSStream;
import org.apache.pdfbox.encoding.DictionaryEncoding;
import org.apache.pdfbox.encoding.Encoding;
import org.apache.pdfbox.encoding.EncodingManager;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDFontFactory;
import org.apache.pdfbox.pdmodel.font.PDType3Font;

public class Type3FontValidator extends AbstractFontValidator {
  protected PDType3Font pdType3 = null;

  protected COSBase fontBBox = null;
  protected COSBase fontMatrix = null;
  protected COSBase charProcs = null;
  protected COSBase fontEncoding = null;
  protected COSBase firstChar = null;
  protected COSBase lastChar = null;
  protected COSBase widths = null;
  protected COSBase toUnicode = null;
  protected COSBase resources = null;

  protected Encoding type3Encoding = null;

  public Type3FontValidator(DocumentHandler handler, COSObject obj)
      throws ValidationException {
    super(handler, obj);
    this.pdType3 = (PDType3Font) this.pFont;
  }

  /**
   * This methods stores in attributes all required element. We extract these
   * elements because of the PDType3Font object returns sometime default value
   * if the field is missing, so to avoid mistake during required field
   * validation we store them.
   */
  private void extractFontDictionaryEntries() {
    // ---- required elements
    this.fontBBox = this.fDictionary.getItem(COSName
        .getPDFName(FONT_DICTIONARY_KEY_FONTBBOX));
    this.fontMatrix = this.fDictionary.getItem(COSName
        .getPDFName(FONT_DICTIONARY_KEY_FONTMATRIX));
    this.charProcs = this.fDictionary.getItem(COSName
        .getPDFName(FONT_DICTIONARY_KEY_CHARPROCS));
    this.fontEncoding = this.fDictionary.getItem(COSName
        .getPDFName(FONT_DICTIONARY_KEY_ENCODING));
    this.firstChar = this.fDictionary.getItem(COSName
        .getPDFName(FONT_DICTIONARY_KEY_FIRSTCHAR));
    this.lastChar = this.fDictionary.getItem(COSName
        .getPDFName(FONT_DICTIONARY_KEY_LASTCHAR));
    this.widths = this.fDictionary.getItem(COSName
        .getPDFName(FONT_DICTIONARY_KEY_WIDTHS));

    // ---- Optional elements
    this.toUnicode = this.fDictionary.getItem(COSName
        .getPDFName(FONT_DICTIONARY_KEY_TOUNICODE));
    this.resources = this.fDictionary.getItem(COSName
        .getPDFName(DICTIONARY_KEY_RESOURCES));
  }

  /**
   * Returns true if all required fields are present. Otherwise, this method
   * returns false and the FontContainer is updated.
   *
   * @return
   */
  private boolean checkMandatoryFields() {
    boolean all = (this.fontBBox != null);
    all = all && (this.fontMatrix != null);
    all = all && (this.charProcs != null);
    all = all && (this.fontEncoding != null);
    all = all && (this.firstChar != null);
    all = all && (this.lastChar != null);
    all = all && (this.widths != null);
    if (!all) {
      this.fontContainer.addError(new ValidationError(
          ERROR_FONTS_DICTIONARY_INVALID));
    }

    /*
     * ---- Since PDF 1.5 : FontDescriptor is mandatory for Type3 font. However
     * because of the FontDescriptor is optional in PDF-1.4 no specific checks
     * are processed for PDF/A validation.
     */
    return all;
  }

  /**
   * FontBBox and FontMatrix are required. This method checks the type and the
   * content of the FontBBox and FontMatrix element (Array of 4/6 number). If a
   * type is invalid, the FontContainer is updated and the method returns false.
   *
   * @return
   */
  private boolean checkFontBBoxMatrix() {
    COSDocument cDoc = this.handler.getDocument().getDocument();

    // ---- both elements are an array
    if (!COSUtils.isArray(this.fontBBox, cDoc)) {
      this.fontContainer
      .addError(new ValidationError(ERROR_FONTS_DICTIONARY_INVALID,
          "The FontBBox element isn't an array"));
      return false;
    }

    if (!COSUtils.isArray(this.fontMatrix, cDoc)) {
      this.fontContainer.addError(new ValidationError(
          ERROR_FONTS_DICTIONARY_INVALID,
          "The FontMatrix element isn't an array"));
      return false;
    }

    // ---- check the content of the FontBBox.
    // ---- Should be an array with 4 numbers
    COSArray bbox = COSUtils.getAsArray(fontBBox, cDoc);
    if (bbox.size() != 4) {
      this.fontContainer.addError(new ValidationError(
          ERROR_FONTS_DICTIONARY_INVALID, "The FontBBox element is invalid"));
      return false;
    } else {
      for (int i = 0; i < 4; i++) {
        COSBase elt = bbox.get(i);
        if (!(COSUtils.isFloat(elt, cDoc) || COSUtils.isInteger(elt, cDoc))) {
          this.fontContainer.addError(new ValidationError(
              ERROR_FONTS_DICTIONARY_INVALID,
              "An element of FontBBox isn't a number"));
          return false;
        }
      }
    }

    // ---- check the content of the FontMatrix.
    // ---- Should be an array with 6 numbers
    COSArray matrix = COSUtils.getAsArray(fontMatrix, cDoc);
    if (matrix.size() != 6) {
      this.fontContainer.addError(new ValidationError(
          ERROR_FONTS_DICTIONARY_INVALID, "The FontMatrix element is invalid"));
      return false;
    } else {
      for (int i = 0; i < 6; i++) {
        COSBase elt = matrix.get(i);
        if (!(COSUtils.isFloat(elt, cDoc) || COSUtils.isInteger(elt, cDoc))) {
          this.fontContainer.addError(new ValidationError(
              ERROR_FONTS_DICTIONARY_INVALID,
              "An element of FontMatrix isn't a number"));
          return false;
        }
      }
    }

    return true;
  }

  /**
   * For a Type3 font, the mapping between the Character Code and the Character
   * name is entirely defined in the Encoding Entry. The Encoding Entry can be a
   * Name (For the 5 predefined Encoding) or a Dictionary. If it is a
   * dictionary, the "Differences" array contains the correspondence between a
   * character code and a set of character name which are different from the
   * encoding entry of the dictionary.
   *
   * This method checks that the encoding is :
   * <UL>
   * <li>An existing encoding name.
   * <li>A dictionary with an existing encoding name (the name is optional) and
   * a well formed "Differences" array (the array is optional)
   * </UL>
   *
   * @return
   */
  private boolean checkEncoding() {
    COSDocument cDoc = this.handler.getDocument().getDocument();

    EncodingManager emng = new EncodingManager();
    if (COSUtils.isString(this.fontEncoding, cDoc)) {
      // ---- Encoding is a Name, check if it is an Existing Encoding
      String enc = COSUtils.getAsString(this.fontEncoding, cDoc);
      try {
        type3Encoding = emng.getEncoding(COSName.getPDFName(enc));
      } catch (IOException e) {
        // ---- the encoding doesn't exist
        this.fontContainer.addError(new ValidationError(ERROR_FONTS_ENCODING));
        return false;
      }
    } else if (COSUtils.isDictionary(this.fontEncoding, cDoc)) {
      COSDictionary encodingDictionary = COSUtils.getAsDictionary(
          this.fontEncoding, cDoc);
      try {
        type3Encoding = new DictionaryEncoding(encodingDictionary);
      } catch (IOException e) {
        // ---- the encoding doesn't exist
        this.fontContainer.addError(new ValidationError(ERROR_FONTS_ENCODING));
        return false;
      }

      COSBase diff = encodingDictionary.getItem(COSName
          .getPDFName(FONT_DICTIONARY_KEY_DIFFERENCES));
      if (diff != null) {
        if (!COSUtils.isArray(diff, cDoc)) {
          this.fontContainer
          .addError(new ValidationError(ERROR_FONTS_TYPE3_DAMAGED,
              "The differences element of the encoding dictionary isn't an array"));
          return false;
        }

        // ---- The DictionaryEncoding object doesn't throw exception if the
        // Differences isn't well formed.
        // So check if the array has the right format.
        COSArray differences = COSUtils.getAsArray(diff, cDoc);
        for (int i = 0; i < differences.size(); ++i) {
          COSBase item = differences.get(i);
          if (!(item instanceof COSInteger || item instanceof COSName)) {
            // ---- Error, the Differences array is invalid
            this.fontContainer
            .addError(new ValidationError(ERROR_FONTS_TYPE3_DAMAGED,
                "Differences Array should contain COSInt or COSName, no other type"));
            return false;
          }
        }
      }
    } else {
      // ---- the encoding entry is invalid
      this.fontContainer.addError(new ValidationError(
          ERROR_FONTS_TYPE3_DAMAGED,
          "The Encoding entry doesn't have the right type"));
      return false;
    }

    return true;
  }

  /**
   * CharProcs is a dictionary where the key is a character name and the value
   * is a Stream which contains the glyph representation of the key.
   *
   * This method checks that all character code defined in the Widths Array
   * exist in the CharProcs dictionary. If the CharProcs doesn't know the
   * Character, it is mapped with the .notdef one.
   *
   * For each character, the Glyph width must be the same as the Width value
   * declared in the Widths array.
   *
   * @param errors
   * @return
   */
  private boolean checkCharProcsAndMetrics() throws ValidationException {
    COSDocument cDoc = this.handler.getDocument().getDocument();

    // ---- the Widths value can be a reference to an object
    // ---- Access the object using the COSkey
    COSArray wArr = COSUtils.getAsArray(this.widths, cDoc);
    if (wArr == null) {
      this.fontContainer.addError(new ValidationError(
          ERROR_FONTS_DICTIONARY_INVALID,
          "The Witdhs array is unreachable"));
      return false;
    }

    COSDictionary charProcsDictionary = COSUtils.getAsDictionary(this.charProcs, cDoc);
    if (charProcsDictionary == null) {
      this.fontContainer.addError(new ValidationError(
          ERROR_FONTS_DICTIONARY_INVALID,
          "The CharProcs element isn't a dictionary"));
      return false;
    }

    // ---- firstChar and lastChar must be integer.
    int fc = ((COSInteger) this.firstChar).intValue();
    int lc = ((COSInteger) this.lastChar).intValue();

    // ---- wArr length = (lc - fc) +1 and it is an array of int.
    // ---- If FirstChar is greater than LastChar, the validation will fail
    // because of
    // ---- the array will have an expected size <= 0.
    int expectedLength = (lc - fc) + 1;
    if (wArr.size() != expectedLength) {
      this.fontContainer.addError(new ValidationError(
          ERROR_FONTS_DICTIONARY_INVALID,
          "The length of Witdhs array is invalid. Expected : \""
              + expectedLength + "\" Current : \"" + wArr.size() + "\""));
      return false;
    }

    // ---- Check width consistency
    for (int i = 0; i < expectedLength; i++) {
      int cid = fc + i;
      COSBase arrContent = wArr.get(i);
      if (COSUtils.isNumeric(arrContent, cDoc)) {
        float width = COSUtils.getAsFloat(arrContent, cDoc);

        String charName = null;
        try {
          charName = this.type3Encoding.getName(cid);
        } catch (IOException e) {
          // shouldn't occur
          throw new ValidationException("Unable to check Widths consistency", e);
        }

        COSBase item = charProcsDictionary.getItem(COSName.getPDFName(charName));
        COSStream charStream = COSUtils.getAsStream(item, cDoc);
        if (charStream == null) {
          /* There are no character description, we declare the Glyph as Invalid.
           * If the character is used in a Stream, the GlyphDetail will throw an exception.
           */
          GlyphException glyphEx = new GlyphException(ERROR_FONTS_METRICS, cid,
              "The CharProcs \"" + charName
              + "\" doesn't exist, the width defines in the Font Dictionary is " + width);
          GlyphDetail glyphDetail = new GlyphDetail(cid, glyphEx);
          this.fontContainer.addKnownCidElement(glyphDetail);
        } else {
          try {
            // --- Parse the Glyph description to obtain the Width
            PDFAType3StreamParser parser = new PDFAType3StreamParser(
                this.handler);
            PDResources pRes = null;
            if (this.resources != null) {
              COSDictionary resAsDict = COSUtils.getAsDictionary(
                  this.resources, cDoc);
              if (resAsDict != null) {
                pRes = new PDResources(resAsDict);
              }
            }
            parser.resetEngine();
            parser.processSubStream(null, pRes, charStream);

            if (width != parser.getWidth()) {
              GlyphException glyphEx = new GlyphException(ERROR_FONTS_METRICS, cid,
                  "The CharProcs \"" + charName
                  + "\" should have a width equals to " + width);
              GlyphDetail glyphDetail = new GlyphDetail(cid, glyphEx);
              this.fontContainer.addKnownCidElement(glyphDetail);
            } else {
              // Glyph is OK, we keep the CID.
              GlyphDetail glyphDetail = new GlyphDetail(cid);
              this.fontContainer.addKnownCidElement(glyphDetail);
            }
          } catch (ContentStreamException e) {
            this.fontContainer.addError(new ValidationError(e
                .getValidationError()));
            return false;
          } catch (IOException e) {
            this.fontContainer.addError(new ValidationError(
                ERROR_FONTS_TYPE3_DAMAGED,
                "The CharProcs references an element which can't be read"));
            return false;
          }
        }

      } else {
        this.fontContainer.addError(new ValidationError(
            ERROR_FONTS_DICTIONARY_INVALID,
            "The Witdhs array is invalid. (some element aren't integer)"));
        return false;
      }
    }
    return true;
  }

  /**
   * If the Resources entry is present, this method check its content. Only
   * fonts and Images are checked because this resource describes glyphs. REMARK
   * : The font and the image aren't validated because they will be validated by
   * an other ValidationHelper.
   *
   * @return
   */
  private boolean checkResources() throws ValidationException {
    if (this.resources == null) {
      // ---- No resources dictionary.
      return true;
    }

    COSDocument cDoc = this.handler.getDocument().getDocument();
    COSDictionary dictionary = COSUtils.getAsDictionary(this.resources, cDoc);
    if (dictionary == null) {
      this.fontContainer.addError(new ValidationError(
          ERROR_FONTS_DICTIONARY_INVALID,
          "The Resources element isn't a dictionary"));
      return false;
    }

    COSBase cbImg = dictionary.getItem(COSName
        .getPDFName(DICTIONARY_KEY_XOBJECT));
    COSBase cbFont = dictionary
        .getItem(COSName.getPDFName(DICTIONARY_KEY_FONT));



    if (cbImg != null) {
      // ---- the referenced objects must be present in the PDF file
      COSDictionary dicImgs = COSUtils.getAsDictionary(cbImg, cDoc);
      Set<COSName> keyList = dicImgs.keySet();
      for (Object key : keyList) {

        COSBase item = dictionary.getItem((COSName) key);
        COSDictionary xObjImg = COSUtils.getAsDictionary(item, cDoc);
        if (xObjImg == null) {
          this.fontContainer.addError(new ValidationError(
              ERROR_FONTS_DICTIONARY_INVALID,
              "The Resources dictionary of type 3 font is invalid"));
          return false;
        }

        if (!XOBJECT_DICTIONARY_VALUE_SUBTYPE_IMG.equals(xObjImg
            .getString(COSName.getPDFName(DICTIONARY_KEY_SUBTYPE)))) {
          this.fontContainer.addError(new ValidationError(
              ERROR_FONTS_DICTIONARY_INVALID,
              "The Resources dictionary of type 3 font is invalid"));
          return false;
        }
      }
    }

    if (cbFont != null) {
      // ---- the referenced object must be present in the PDF file
      COSDictionary dicFonts = COSUtils.getAsDictionary(cbFont, cDoc);
      Set<COSName> keyList = dicFonts.keySet();
      for (Object key : keyList) {

        COSBase item = dictionary.getItem((COSName) key);
        COSDictionary xObjFont = COSUtils.getAsDictionary(item, cDoc);
        if (xObjFont == null) {
          this.fontContainer.addError(new ValidationError(
              ERROR_FONTS_DICTIONARY_INVALID,
              "The Resources dictionary of type 3 font is invalid"));
          return false;
        }

        if (!FONT_DICTIONARY_VALUE_FONT.equals(xObjFont.getString(COSName
            .getPDFName(DICTIONARY_KEY_TYPE)))) {
          this.fontContainer.addError(new ValidationError(
              ERROR_FONTS_DICTIONARY_INVALID,
              "The Resources dictionary of type 3 font is invalid"));
          return false;
        }

        try {
          PDFont aFont = PDFontFactory.createFont(xObjFont);
          // FontContainer aContainer = this.handler.retrieveFontContainer(aFont);
          AbstractFontContainer aContainer = this.handler.getFont(aFont.getCOSObject());
          // ---- another font is used in the Type3, check if the font is valid.
          if (aContainer.isValid() != State.VALID) {
            this.fontContainer
            .addError(new ValidationError(ERROR_FONTS_TYPE3_DAMAGED,
                "The Resources dictionary of type 3 font contains invalid font"));
            return false;
          }
        } catch (IOException e) {
          throw new ValidationException("Unable to valid the Type3 : "
              + e.getMessage());
        }
      }
    }

    List<ValidationError> errors = new ArrayList<ValidationError>();
    ExtGStateContainer extGStates = new ExtGStateContainer(dictionary, cDoc);
    boolean res = extGStates.validateTransparencyRules(errors);
    for (ValidationError err : errors) {
      this.fontContainer.addError(err);
    }

    return res && validateShadingPattern(dictionary, errors);
  }

  /**
   * This method check the Shading entry of the resource dictionary if exists.
   *
   * @param result
   * @return
   * @throws ValidationException
   */
  protected boolean validateShadingPattern(COSDictionary dictionary,
      List<ValidationError> result) throws ValidationException {
    boolean res = true;
    if (dictionary != null) {
      COSDictionary shadings = (COSDictionary) dictionary
          .getDictionaryObject(PATTERN_KEY_SHADING);
      if (shadings != null) {
        for (COSName key : shadings.keySet()) {
          COSDictionary aShading = (COSDictionary) shadings.getDictionaryObject(key);
          ShadingPattern sp = new ShadingPattern(handler, aShading);
          List<ValidationError> lErrors = sp.validate();
          if (lErrors != null && !lErrors.isEmpty()) {
            result.addAll(lErrors);
            res = false;
          }
        }
      }
    }
    return res;
  }

  /*
   * (non-Javadoc)
   *
   * @see net.awl.edoc.pdfa.validation.font.FontValidator#validate()
   */
  public boolean validate() throws ValidationException {
    extractFontDictionaryEntries();
    boolean isValid = checkMandatoryFields();
    isValid = isValid && checkFontBBoxMatrix();
    isValid = isValid && checkEncoding();
    isValid = isValid && checkCharProcsAndMetrics();
    isValid = isValid && checkResources();
    return isValid;
  }
}
TOP

Related Classes of org.apache.padaf.preflight.font.Type3FontValidator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.