All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.verapdf.features.gf.GFFeatureParser Maven / Gradle / Ivy

The newest version!
/**
 * This file is part of veraPDF Feature Reporting, a module of the veraPDF project.
 * Copyright (c) 2015, veraPDF Consortium 
 * All rights reserved.
 *
 * veraPDF Feature Reporting is free software: you can redistribute it and/or modify
 * it under the terms of either:
 *
 * The GNU General public license GPLv3+.
 * You should have received a copy of the GNU General Public License
 * along with veraPDF Feature Reporting as the LICENSE.GPL file in the root of the source
 * tree.  If not, see http://www.gnu.org/licenses/ or
 * https://www.gnu.org/licenses/gpl-3.0.en.html.
 *
 * The Mozilla Public License MPLv2+.
 * You should have received a copy of the Mozilla Public License along with
 * veraPDF Feature Reporting as the LICENSE.MPL file in the root of the source tree.
 * If a copy of the MPL was not distributed with this file, you can obtain one at
 * http://mozilla.org/MPL/2.0/.
 */
package org.verapdf.features.gf;

import org.verapdf.as.ASAtom;
import org.verapdf.cos.*;
import org.verapdf.external.ICCProfile;
import org.verapdf.factory.colors.ColorSpaceFactory;
import org.verapdf.features.*;
import org.verapdf.features.objects.ActionFeaturesObjectAdapter;
import org.verapdf.features.tools.ErrorsHelper;
import org.verapdf.features.tools.FeatureTreeNode;
import org.verapdf.pd.*;
import org.verapdf.pd.actions.*;
import org.verapdf.pd.colors.PDColorSpace;
import org.verapdf.pd.colors.PDICCBased;
import org.verapdf.pd.encryption.StandardSecurityHandler;
import org.verapdf.pd.font.PDCIDFont;
import org.verapdf.pd.font.PDFont;
import org.verapdf.pd.font.PDType0Font;
import org.verapdf.pd.font.type3.PDType3Font;
import org.verapdf.pd.form.PDAcroForm;
import org.verapdf.pd.form.PDFormField;
import org.verapdf.pd.form.PDSignatureField;
import org.verapdf.pd.images.PDXForm;
import org.verapdf.pd.images.PDXImage;
import org.verapdf.pd.images.PDXObject;
import org.verapdf.pd.patterns.PDPattern;
import org.verapdf.pd.patterns.PDShading;
import org.verapdf.pd.patterns.PDShadingPattern;
import org.verapdf.pd.patterns.PDTilingPattern;
import org.verapdf.tools.PageLabels;

import java.io.IOException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Parses GreenField PDDocument to generate features collection
 *
 * @author Maksim Bezrukov
 */
public final class GFFeatureParser {
	private static final EnumSet XOBJECTS = EnumSet.of(FeatureObjectType.FORM_XOBJECT,
			FeatureObjectType.IMAGE_XOBJECT, FeatureObjectType.POSTSCRIPT_XOBJECT);
	private static final Logger LOGGER = Logger.getLogger(GFFeatureParser.class.getCanonicalName());
	private static final String ID = "id";
	private static final String DEVICEGRAY_ID = "devgray";
	private static final String DEVICERGB_ID = "devrgb";
	private static final String DEVICECMYK_ID = "devcmyk";

	private final FeaturesReporter reporter;
	private final FeatureExtractorConfig config;
	private final Set processedIDs;

	private GFFeatureParser(FeaturesReporter reporter, FeatureExtractorConfig config) {
		this.reporter = reporter;
		this.config = config;
		this.processedIDs = new HashSet<>();
	}

	/**
	 * Parses the document and returns Feature collection by using given
	 * Features Reporter
	 *
	 * @param document the document for parsing
	 * @return FeaturesCollection class with information about all featurereport
	 */
	public static FeatureExtractionResult getFeaturesCollection(final PDDocument document, final FeatureExtractorConfig config) {

		FeaturesReporter reporter = new FeaturesReporter(config);
		return getFeatures(document, reporter, config);
	}

	/**
	 * Parses the document and returns Feature collection by using given
	 * Features Reporter
	 *
	 * @param document the document for parsing
	 * @return FeaturesCollection class with information about all featurereport
	 */
	public static FeatureExtractionResult getFeaturesCollection(final PDDocument document,
																final List extractors, final FeatureExtractorConfig config) {

		FeaturesReporter reporter = new FeaturesReporter(config, extractors);
		return getFeatures(document, reporter, config);
	}

	private static FeatureExtractionResult getFeatures(PDDocument document, FeaturesReporter reporter,
													   FeatureExtractorConfig config) {
		if (config == null) {
			throw new IllegalArgumentException("Features config can not be null");
		}
		if (document != null) {
			GFFeatureParser parser = new GFFeatureParser(reporter, config);
			parser.parseDocumentFeatures(document);
		}

		return reporter.getCollection();
	}

	private void parseDocumentFeatures(PDDocument document) {
		COSDocument cosDocument = document.getDocument();

		COSTrailer trailer = cosDocument.getTrailer();
		if (trailer != null) {
			reporter.report(GFFeaturesObjectCreator.createInfoDictFeaturesObject(trailer.getInfo()));
		}

		StandardSecurityHandler standardSecurityHandler = cosDocument.getStandardSecurityHandler();
		if (standardSecurityHandler != null) {
			reporter.report(GFFeaturesObjectCreator.createDocSecurityFeaturesObject(standardSecurityHandler.getPdEncryption()));
		}

		try {
			PDCatalog catalog = document.getCatalog();
			if (catalog != null) {
				getCatalogFeatures(catalog);
			}
		} catch (IOException e) {
			LOGGER.log(Level.SEVERE, "Problem in parsing document catalog", e);
		}

		reporter.report(GFFeaturesObjectCreator.createLowLvlInfoFeaturesObject(cosDocument));

	}

	private void getCatalogFeatures(PDCatalog catalog) throws IOException {
		reporter.report(GFFeaturesObjectCreator.createMetadataFeaturesObject(catalog.getMetadata()));
		PDOutlineDictionary outlines = catalog.getOutlines();
		reporter.report(GFFeaturesObjectCreator.createOutlinesFeaturesObject(outlines));

		PDNamesDictionary namesDictionary = catalog.getNamesDictionary();

		if (config.isFeatureEnabled(FeatureObjectType.ACTION)) {
			if (outlines != null) {
				reportOutlinesActions(outlines.getFirst());
			}
			reportAction(catalog.getOpenAction(), ActionFeaturesObjectAdapter.Location.DOCUMENT);
			PDCatalogAdditionalActions additionalActions = catalog.getAdditionalActions();
			if (additionalActions != null) {
				List actions = additionalActions.getActions();
				for (PDAction action : actions) {
					reportAction(action, ActionFeaturesObjectAdapter.Location.DOCUMENT);
				}
			}
			if (namesDictionary != null) {
				PDNameTreeNode javaScript = namesDictionary.getJavaScript();
				if (javaScript != null) {
					reportJavaScripts(javaScript);
				}
			}
		}

		if (config.isFeatureEnabled(FeatureObjectType.EMBEDDED_FILE) && namesDictionary != null) {
			PDNameTreeNode node = namesDictionary.getEmbeddedFiles();
			if (node != null) {
				reportEmbeddedFileNode(node, 0);
			}
		}

		PDAcroForm acroForm = catalog.getAcroForm();
		if (acroForm != null) {
			getAcroFormFeatures(acroForm);
		}

		if (catalog.getOutputIntents() != null) {
			for (PDOutputIntent outInt : catalog.getOutputIntents()) {
				String iccProfileID = addICCProfileFromOutputIntent(outInt);
				if (!config.isFeatureEnabled(FeatureObjectType.ICCPROFILE)) {
					iccProfileID = null;
				}
				reporter.report(GFFeaturesObjectCreator.createOutputIntentFeaturesObject(outInt, iccProfileID));
			}
		}

		PDPageTree pageTree = catalog.getPageTree();
		if (pageTree != null) {
			getPageTreeFeatures(pageTree, catalog.getPageLabels());
		}
	}

	private void reportOutlinesActions(PDOutlineItem outline) {
		if (outline != null) {
			reportAction(outline.getAction(), ActionFeaturesObjectAdapter.Location.OUTLINES);
			reportOutlinesActions(outline.getFirst());
			reportOutlinesActions(outline.getNext());
		}
	}

	private void reportAction(PDAction action, ActionFeaturesObjectAdapter.Location location) {
		if (action != null) {
			reporter.report(GFFeaturesObjectCreator.createActionFeaturesObject(action, location));
			for (PDAction next : action.getNext()) {
				reportAction(next, location);
			}
		}
	}

	private void getAcroFormFeatures(PDAcroForm acroForm) {
		List fields = acroForm.getFields();
		for (PDFormField field : fields) {
			getSignatureFeatures(field);
			getRootFormFieldFeatures(field);
		}
	}

	private void getRootFormFieldFeatures(PDFormField field) {
		if (field == null) {
			return;
		}
		if (config.isFeatureEnabled(FeatureObjectType.INTERACTIVE_FORM_FIELDS)) {
			reporter.report(GFFeaturesObjectCreator.createInteractiveFormFieldFeaturesObject(field));
		}
		if (config.isFeatureEnabled(FeatureObjectType.ACTION)) {
			getFormFieldActions(field);
		}
	}

	private void getFormFieldActions(PDFormField field) {
		PDFormFieldActions additionalActions = field.getActions();
		if (additionalActions != null) {
			List actions = additionalActions.getActions();
			for (PDAction action : actions) {
				reportAction(action, ActionFeaturesObjectAdapter.Location.INTERACTIVE_FORM_FIELD);
			}
		}
		for (PDFormField child : field.getChildFormFields()) {
			if (child != null) {
				getFormFieldActions(child);
			}
		}
	}

	private void getSignatureFeatures(PDFormField field) {
		if (config.isFeatureEnabled(FeatureObjectType.SIGNATURE) && field.getFT() == ASAtom.SIG) {
			PDSignature signature = ((PDSignatureField) field).getSignature();
			if (signature != null) {
				reporter.report(GFFeaturesObjectCreator.createSignatureFeaturesObject(signature));
			}
		}
	}

	private void getPageTreeFeatures(PDPageTree pageTree, PageLabels pageLabels) {
		for (int i = 0; i < pageTree.getPageCount(); ++i) {
			PDPage page = pageTree.getPage(i);
			reportPageActions(page);
			Set annotsId = addAnnotsDependencies(page);
			annotsId = config.isFeatureEnabled(FeatureObjectType.ANNOTATION) ? annotsId : null;

			String thumbID = null;
			PDResources resources = page.getResources();
			COSObject thumb = page.getKey(ASAtom.THUMB);
			if (thumb != null) {
				thumbID = getId(thumb, FeatureObjectType.IMAGE_XOBJECT);
				if (checkIDBeforeProcess(thumbID)) {
					if (thumb.getType() == COSObjType.COS_STREAM) {
						PDXImage img = new PDXImage(thumb, resources);
						parseImageXObject(img, thumbID);
					} else {
						xobjectCreationProblem(thumbID, "Thumb is not a stream");
					}
				}
			}
			thumbID = config.isAnyFeatureEnabled(XOBJECTS) ? thumbID : null;

			Set extGStateChild = config.isFeatureEnabled(FeatureObjectType.EXT_G_STATE)
					? parseExGStateFromResource(resources) : null;
			Set colorSpaceChild = config.isFeatureEnabled(FeatureObjectType.COLORSPACE)
					? parseColorSpaceFromResources(resources) : null;
			Set patternChild = config.isFeatureEnabled(FeatureObjectType.PATTERN)
					? parsePatternFromResource(resources) : null;
			Set shadingChild = config.isFeatureEnabled(FeatureObjectType.SHADING)
					? parseShadingFromResource(resources) : null;
			Set xobjectChild = config.isAnyFeatureEnabled(XOBJECTS) ? parseXObjectFromResources(resources)
					: null;
			Set fontChild = config.isFeatureEnabled(FeatureObjectType.FONT) ? parseFontFromResources(resources)
					: null;
			Set propertiesChild = config.isFeatureEnabled(FeatureObjectType.PROPERTIES)
					? parsePropertiesFromResources(resources) : null;

			int pageNumber = page.getPageNumber();
			String label = pageLabels == null ? null : pageLabels.getLabel(pageNumber);
			reporter.report(GFFeaturesObjectCreator.createPageFeaturesObject(page, label, thumbID, annotsId, extGStateChild,
					colorSpaceChild, patternChild, shadingChild, xobjectChild, fontChild, propertiesChild,
					pageNumber));
		}
	}

	private void reportPageActions(PDPage page) {
		if (config.isFeatureEnabled(FeatureObjectType.ACTION)) {
			PDPageAdditionalActions additionalActions = page.getAdditionalActions();
			if (additionalActions != null) {
				List actions = additionalActions.getActions();
				for (PDAction action : actions) {
					reportAction(action, ActionFeaturesObjectAdapter.Location.PAGE);
				}
			}
			Set visitedKeys = new HashSet<>();
			processNavigationNodeActions(page.getPresSteps(), visitedKeys);
		}
	}

	private void processNavigationNodeActions(PDNavigationNode navNode, Set visitedKeys) {
		if (navNode != null) {
			COSKey objectKey = navNode.getObject().getObjectKey();
			if (visitedKeys.contains(objectKey)) {
				return;
			}
			visitedKeys.add(objectKey);
			reportAction(navNode.getNA(), ActionFeaturesObjectAdapter.Location.PAGE);
			reportAction(navNode.getPA(), ActionFeaturesObjectAdapter.Location.PAGE);
			processNavigationNodeActions(navNode.getNext(), visitedKeys);
			processNavigationNodeActions(navNode.getPrev(), visitedKeys);
		}
	}

	private Set addAnnotsDependencies(PDPage page) {
		Set annotsId = new HashSet<>();

		for (PDAnnotation annot : page.getAnnotations()) {
			reportAnnotationActions(annot);

			String id = getId(annot.getObject(), FeatureObjectType.ANNOTATION);
			annotsId.add(id);
			if (checkIDBeforeProcess(id)) {
				PDAnnotation popup = annot.getPopup();
				String popupID = null;
				if (popup != null) {
					popupID = addPopup(popup);
				}

				Set formsIDs = getAnnotationResourcesDependencies(annot);
				popupID = config.isFeatureEnabled(FeatureObjectType.ANNOTATION) ? popupID : null;
				formsIDs = config.isAnyFeatureEnabled(XOBJECTS) ? formsIDs : null;
				reporter.report(
						GFFeaturesObjectCreator.createAnnotFeaturesObject(annot, id, popupID, formsIDs));

			}
		}

		return annotsId;
	}

	private void reportAnnotationActions(PDAnnotation annot) {
		if (config.isFeatureEnabled(FeatureObjectType.ACTION) && annot != null) {
			reportAction(annot.getA(), ActionFeaturesObjectAdapter.Location.ANNOTATION);
			PDAnnotationAdditionalActions additionalActions = annot.getAdditionalActions();
			if (additionalActions != null) {
				List actions = additionalActions.getActions();
				for (PDAction action : actions) {
					reportAction(action, ActionFeaturesObjectAdapter.Location.ANNOTATION);
				}
			}
		}
	}

	private String addPopup(PDAnnotation popup) {
		reportAnnotationActions(popup);
		String id = getId(popup.getObject(), FeatureObjectType.ANNOTATION);

		if (checkIDBeforeProcess(id)) {
			reporter.report(GFFeaturesObjectCreator.createAnnotFeaturesObject(popup, id, null, null));
		}
		return id;
	}

	private Set getAnnotationResourcesDependencies(PDAnnotation annot) {
		Set appearances = new HashSet<>();

		PDAppearanceEntry normalAppearance = annot.getNormalAppearance();
		if (normalAppearance != null) {
			appearances.addAll(getAppearanceEntryDependencies(normalAppearance));
		}

		PDAppearanceEntry rolloverAppearance = annot.getRolloverAppearance();
		if (rolloverAppearance != null) {
			appearances.addAll(getAppearanceEntryDependencies(rolloverAppearance));
		}

		PDAppearanceEntry downAppearance = annot.getDownAppearance();
		if (downAppearance != null) {
			appearances.addAll(getAppearanceEntryDependencies(downAppearance));
		}

		return appearances;
	}

	private Set getAppearanceEntryDependencies(PDAppearanceEntry entry) {
		Set res = new HashSet<>();
		if (entry.isSubDictionary()) {
			for (Map.Entry mapEntry : entry.getSubDictionary().entrySet()) {
				res.add(getAppearanceStreamDependencies(mapEntry.getValue()));
			}
		} else {
			res.add(getAppearanceStreamDependencies(entry.getAppearanceStream()));
		}
		return res;
	}

	private String getAppearanceStreamDependencies(PDAppearanceStream stream) {
		String id = getId(stream.getObject(), FeatureObjectType.FORM_XOBJECT);
		if (checkIDBeforeProcess(id)) {
			parseFormXObject(stream, id);
		}
		return id;
	}

	private void reportJavaScripts(final PDNameTreeNode node) {
		Map names = node.getNames();
		for (COSObject value : names.values()) {
			if (value != null && value.getType().isDictionaryBased()) {
				reportAction(new PDAction(value), ActionFeaturesObjectAdapter.Location.DOCUMENT);
			}
		}
		for (PDNameTreeNode kid : node.getKids()) {
			reportJavaScripts(kid);
		}
	}

	private int reportEmbeddedFileNode(final PDNameTreeNode node, final int index) {
		int res = index;
		Map names = node.getNames();
		for (COSObject value : names.values()) {
			if (value != null && value.getType().isDictionaryBased()) {
				reporter.report(GFFeaturesObjectCreator.createEmbeddedFileFeaturesObject(value, ++res));
			}
		}
		for (PDNameTreeNode kid : node.getKids()) {
			res = reportEmbeddedFileNode(kid, res);
		}
		return res;
	}

	private String addICCProfileFromOutputIntent(PDOutputIntent outInt) {
		ICCProfile profile = outInt.getDestOutputProfile();
		if (profile != null) {
			String iccProfileID = getId(profile.getObject(), FeatureObjectType.ICCPROFILE);
			if (checkIDBeforeProcess(iccProfileID)) {
				reporter.report(GFFeaturesObjectCreator.createICCProfileFeaturesObject(profile, iccProfileID));
			}
			return iccProfileID;
		}
		return null;
	}

	private void xobjectCreationProblem(final String nodeID, String errorMessage) {
		creationProblem(nodeID, errorMessage, FeatureObjectType.FORM_XOBJECT, false);
	}

	private void creationProblem(final String nodeID, final String errorMessage, final FeatureObjectType type, final boolean isTypeError) {
		if (config.isFeatureEnabled(type)) {
			if (!isTypeError) {
				FeatureTreeNode node = createNodeWithType(type);
				if (nodeID != null) {
					node.setAttribute(ID, nodeID);
				}
				reporter.getCollection().addNewFeatureTree(type, node);
				ErrorsHelper.addErrorIntoCollection(reporter.getCollection(), node, errorMessage);
			} else {
				String id = ErrorsHelper.addErrorIntoCollection(reporter.getCollection(), null, errorMessage);
				reporter.getCollection().addNewError(type, id);
			}
		}
	}

	private FeatureTreeNode createNodeWithType(FeatureObjectType type) {
		if (type == FeatureObjectType.FORM_XOBJECT) {
			FeatureTreeNode res = FeatureTreeNode.createRootNode("xobject");
			res.setAttribute("type", "form");
			return res;
		}

		return FeatureTreeNode.createRootNode(type.getNodeName());
	}

	private Set parseColorSpaceFromResources(PDResources resources) {
		if (resources == null || resources.getXObjectNames() == null) {
			return null;
		}

		Set colorSpaceIDs = new HashSet<>();
		for (ASAtom name : resources.getColorSpaceNames()) {
			PDColorSpace colorSpace = resources.getColorSpace(name);
			if (colorSpace != null) {
				String id = getId(colorSpace.getObject(), FeatureObjectType.COLORSPACE);
				id = checkColorSpaceID(id, colorSpace);
				colorSpaceIDs.add(id);
				if (checkIDBeforeProcess(id)) {
					parseColorSpace(colorSpace, id);
				}
			}
		}
		return colorSpaceIDs;
	}

	private Set parseXObjectFromResources(PDResources resources) {
		if (resources == null || resources.getXObjectNames() == null) {
			return null;
		}

		Set xobjectsIDs = new HashSet<>();
		for (ASAtom name : resources.getXObjectNames()) {
			PDXObject xobj = resources.getXObject(name);
			if (xobj != null) {
				String id = getId(xobj.getObject(), FeatureObjectType.IMAGE_XOBJECT);
				xobjectsIDs.add(id);
				if (checkIDBeforeProcess(id)) {
					if (xobj.getType() == ASAtom.IMAGE) {
						parseImageXObject((PDXImage) xobj, id);
					} else if (xobj.getType() == ASAtom.FORM) {
						parseFormXObject((PDXForm) xobj, id);
					} else if (xobj.getType() == ASAtom.PS) {
						reporter.report(GFFeaturesObjectCreator.createPostScriptXObjectFeaturesObject(id));
					}
				}
			}
		}
		return xobjectsIDs;
	}

	private Set parsePropertiesFromResources(PDResources resources) {
		if (resources == null || resources.getPropertiesNames() == null) {
			return null;
		}

		Set propertiesIDs = new HashSet<>();
		for (ASAtom name : resources.getPropertiesNames()) {
			COSObject propBase = resources.getKey(ASAtom.PROPERTIES);
			if (propBase.getType() == COSObjType.COS_DICT) {
				COSObject base = propBase.getKey(name);
				String id = getId(base, FeatureObjectType.PROPERTIES);
				propertiesIDs.add(id);
				if (checkIDBeforeProcess(id)) {
					reporter.report(
							GFFeaturesObjectCreator.createPropertiesDictFeaturesObject(base, id));
				}
			}
		}
		return propertiesIDs;
	}

	private Set parseFontFromResources(PDResources resources) {
		if (resources == null || resources.getFontNames() == null) {
			return null;
		}

		Set fontIDs = new HashSet<>();
		for (ASAtom name : resources.getFontNames()) {
			PDFont font = resources.getFont(name);
			if (font != null) {
				String id = getId(font.getObject(), FeatureObjectType.FONT);
				fontIDs.add(id);
				if (checkIDBeforeProcess(id)) {
					parseFont(font, id);
				}
			}
		}
		return fontIDs;
	}

	private Set parseExGStateFromResource(PDResources resources) {
		if (resources == null || resources.getExtGStateNames() == null) {
			return null;
		}

		Set gStatesIDs = new HashSet<>();
		for (ASAtom name : resources.getExtGStateNames()) {
			PDExtGState exGState = resources.getExtGState(name);
			if (exGState != null) {
				String id = getId(exGState.getObject(), FeatureObjectType.EXT_G_STATE);
				gStatesIDs.add(id);
				if (checkIDBeforeProcess(id)) {
					parseExGState(exGState, id);
				}
			}
		}
		return gStatesIDs;
	}

	private Set parsePatternFromResource(PDResources resources) {
		if (resources == null || resources.getPatternNames() == null) {
			return null;
		}

		Set patternIDs = new HashSet<>();
		for (ASAtom name : resources.getPatternNames()) {
			PDPattern pattern = resources.getPattern(name);
			if (pattern != null) {
				String id = getId(pattern.getObject(), FeatureObjectType.PATTERN);
				patternIDs.add(id);
				if (checkIDBeforeProcess(id)) {
					parsePattern(pattern, id);
				}
			}
		}
		return patternIDs;
	}

	private Set parseShadingFromResource(PDResources resources) {
		if (resources == null || resources.getShadingNames() == null) {
			return null;
		}

		Set shadingIDs = new HashSet<>();
		for (ASAtom name : resources.getShadingNames()) {
			PDShading shading = resources.getShading(name);
			if (shading != null) {
				String id = getId(shading.getObject(), FeatureObjectType.SHADING);
				shadingIDs.add(id);
				if (checkIDBeforeProcess(id)) {
					parseShading(shading, id);
				}
			}
		}
		return shadingIDs;
	}

	private void parseImageXObject(PDXImage xobj, String id) {
		COSObject baseColorSpace = xobj.getKey(ASAtom.CS);
		if (baseColorSpace.empty()) {
			baseColorSpace = xobj.getKey(ASAtom.COLORSPACE);
		}
		String idColorSpace = getId(baseColorSpace, FeatureObjectType.COLORSPACE);
		PDColorSpace colorSpace = ColorSpaceFactory.getColorSpace(baseColorSpace);
		idColorSpace = checkColorSpaceID(idColorSpace, colorSpace);
		if (checkIDBeforeProcess(idColorSpace)) {
			parseColorSpace(colorSpace, idColorSpace);
		}

		String idMask = null;
		PDXImage xobjMask = xobj.getMask();
		if (xobjMask != null) {
			idMask = getId(xobjMask.getObject(), FeatureObjectType.IMAGE_XOBJECT);
			if (checkIDBeforeProcess(idMask)) {
				parseImageXObject(xobjMask, idMask);
			}
		}

		String idSMask = null;
		PDXImage xobjSMask = xobj.getSMask();
		if (xobjSMask != null) {
			idSMask = getId(xobjSMask.getObject(), FeatureObjectType.IMAGE_XOBJECT);
			if (checkIDBeforeProcess(idSMask)) {
				parseImageXObject(xobjSMask, idSMask);
			}
		}

		Set alternatesIDs = new HashSet<>();
		for (PDXImage entry : xobj.getAlternates()) {
			String idImage = getId(entry.getObject(), FeatureObjectType.IMAGE_XOBJECT);
			alternatesIDs.add(idImage);
			if (checkIDBeforeProcess(idImage)) {
				parseImageXObject(entry, idImage);
			}
		}

		idColorSpace = config.isFeatureEnabled(FeatureObjectType.COLORSPACE) ? idColorSpace : null;
		if (!config.isAnyFeatureEnabled(XOBJECTS)) {
			idMask = null;
			idSMask = null;
			alternatesIDs = null;
		}

		reporter.report(GFFeaturesObjectCreator.createImageXObjectFeaturesObject(xobj, id, idColorSpace, idMask,
				idSMask, alternatesIDs));
	}

	private void parseFormXObject(PDXForm xobj, String id) {

		PDGroup group = xobj.getGroup();
		String idColorSpace = null;
		if (group != null && ASAtom.TRANSPARENCY.equals(group.getSubtype())) {
			PDColorSpace colorSpace = group.getColorSpace();
			if (colorSpace != null) {
				idColorSpace = getId(colorSpace.getObject(), FeatureObjectType.COLORSPACE);
				idColorSpace = checkColorSpaceID(idColorSpace, colorSpace);
				if (checkIDBeforeProcess(idColorSpace)) {
					parseColorSpace(colorSpace, idColorSpace);
				}
			}
		}

		PDResources resources = xobj.getResources();
		Set extGStateChild = parseExGStateFromResource(resources);
		extGStateChild = config.isFeatureEnabled(FeatureObjectType.EXT_G_STATE) ? extGStateChild : null;
		Set colorSpaceChild = parseColorSpaceFromResources(resources);
		if (!config.isFeatureEnabled(FeatureObjectType.COLORSPACE)) {
			idColorSpace = null;
			colorSpaceChild = null;
		}
		Set patternChild = config.isFeatureEnabled(FeatureObjectType.PATTERN)
				? parsePatternFromResource(resources) : null;
		Set shadingChild = config.isFeatureEnabled(FeatureObjectType.SHADING)
				? parseShadingFromResource(resources) : null;
		Set xobjectChild = config.isAnyFeatureEnabled(XOBJECTS) ? parseXObjectFromResources(resources) : null;
		Set fontChild = config.isFeatureEnabled(FeatureObjectType.FONT) ? parseFontFromResources(resources)
				: null;
		Set propertiesChild = config.isFeatureEnabled(FeatureObjectType.PROPERTIES)
				? parsePropertiesFromResources(resources) : null;

		reporter.report(GFFeaturesObjectCreator.createFormXObjectFeaturesObject(xobj, id, idColorSpace, extGStateChild,
				colorSpaceChild, patternChild, shadingChild, xobjectChild, fontChild, propertiesChild));

	}

	private void parseExGState(PDExtGState exGState, String id) {
		String childFontID = null;
		PDFont font = exGState.getFont();
		if (font != null) {
			childFontID = getId(font.getObject(), FeatureObjectType.FONT);
			if (checkIDBeforeProcess(childFontID)) {
				parseFont(font, childFontID);
			}
		}

		childFontID = config.isFeatureEnabled(FeatureObjectType.FONT) ? childFontID : null;
		reporter.report(GFFeaturesObjectCreator.createExtGStateFeaturesObject(exGState, id, childFontID));
	}

	private void parsePattern(PDPattern pattern, String id) {
		if (pattern.getPatternType() == 1) {
			PDTilingPattern tilingPattern = (PDTilingPattern) pattern;
			PDResources resources = tilingPattern.getResources();
			Set extGStateChild = config.isFeatureEnabled(FeatureObjectType.EXT_G_STATE)
					? parseExGStateFromResource(resources) : null;
			Set colorSpaceChild = config.isFeatureEnabled(FeatureObjectType.COLORSPACE)
					? parseColorSpaceFromResources(resources) : null;
			Set patternChild = config.isFeatureEnabled(FeatureObjectType.PATTERN)
					? parsePatternFromResource(resources) : null;
			Set shadingChild = config.isFeatureEnabled(FeatureObjectType.SHADING)
					? parseShadingFromResource(resources) : null;
			Set xobjectChild = config.isAnyFeatureEnabled(XOBJECTS) ? parseXObjectFromResources(resources)
					: null;
			Set fontChild = config.isFeatureEnabled(FeatureObjectType.FONT) ? parseFontFromResources(resources)
					: null;
			Set propertiesChild = config.isFeatureEnabled(FeatureObjectType.PROPERTIES)
					? parsePropertiesFromResources(resources) : null;

			reporter.report(GFFeaturesObjectCreator.createTilingPatternFeaturesObject(tilingPattern, id, extGStateChild,
					colorSpaceChild, patternChild, shadingChild, xobjectChild, fontChild, propertiesChild));
		} else if (pattern.getPatternType() == 2) {
			PDShadingPattern shadingPattern = (PDShadingPattern) pattern;
			String shadingID = null;
			PDShading shading = shadingPattern.getShading();
			if (shading != null) {
				shadingID = getId(shading.getObject(), FeatureObjectType.SHADING);
				if (checkIDBeforeProcess(shadingID)) {
					parseShading(shading, shadingID);
				}
			}

			String exGStateID = null;
			PDExtGState extGState = shadingPattern.getExtGState();
			if (extGState != null) {
				exGStateID = getId(extGState.getObject(), FeatureObjectType.EXT_G_STATE);
				if (checkIDBeforeProcess(exGStateID)) {
					parseExGState(extGState, exGStateID);
				}
			}

			shadingID = config.isFeatureEnabled(FeatureObjectType.SHADING) ? shadingID : null;
			exGStateID = config.isFeatureEnabled(FeatureObjectType.EXT_G_STATE) ? exGStateID : null;
			reporter.report(GFFeaturesObjectCreator.createShadingPatternFeaturesObject(shadingPattern, id, shadingID,
					exGStateID));
		}
	}

	private void parseShading(PDShading shading, String id) {
		COSObject base = shading.getKey(ASAtom.CS);
		if (base.empty()) {
			base = shading.getKey(ASAtom.COLORSPACE);
		}
		String colorspaceID = getId(base, FeatureObjectType.COLORSPACE);
		PDColorSpace colorSpace = ColorSpaceFactory.getColorSpace(base);
		colorspaceID = checkColorSpaceID(colorspaceID, colorSpace);
		if (checkIDBeforeProcess(colorspaceID)) {
			parseColorSpace(colorSpace, colorspaceID);
		}

		colorspaceID = config.isFeatureEnabled(FeatureObjectType.COLORSPACE) ? colorspaceID : null;
		reporter.report(GFFeaturesObjectCreator.createShadingFeaturesObject(shading, id, colorspaceID));
	}

	private void parseFont(PDFont font, String id) {
		if (font.getSubtype() == ASAtom.TYPE3) {
			PDResources resources = ((PDType3Font) font).getResources();
			Set extGStateChild = config.isFeatureEnabled(FeatureObjectType.EXT_G_STATE)
					? parseExGStateFromResource(resources) : null;
			Set colorSpaceChild = config.isFeatureEnabled(FeatureObjectType.COLORSPACE)
					? parseColorSpaceFromResources(resources) : null;
			Set patternChild = config.isFeatureEnabled(FeatureObjectType.PATTERN)
					? parsePatternFromResource(resources) : null;
			Set shadingChild = config.isFeatureEnabled(FeatureObjectType.SHADING)
					? parseShadingFromResource(resources) : null;
			Set xobjectChild = config.isAnyFeatureEnabled(XOBJECTS) ? parseXObjectFromResources(resources)
					: null;
			Set fontChild = config.isFeatureEnabled(FeatureObjectType.FONT) ? parseFontFromResources(resources)
					: null;
			Set propertiesChild = config.isFeatureEnabled(FeatureObjectType.PROPERTIES)
					? parsePropertiesFromResources(resources) : null;

			reporter.report(GFFeaturesObjectCreator.createFontFeaturesObject(font, id, extGStateChild, colorSpaceChild,
					patternChild, shadingChild, xobjectChild, fontChild, propertiesChild));
		} else if (font.getSubtype() == ASAtom.TYPE0) {
			PDType0Font type0 = (PDType0Font) font;

			COSObject descendantFontsBase = type0.getDescendantFontObject();
			if (descendantFontsBase != null) {
				String descendantID = getId(descendantFontsBase, FeatureObjectType.FONT);
				if (checkIDBeforeProcess(descendantID)) {
					parseFont(new PDCIDFont((COSDictionary) descendantFontsBase.getDirectBase(),
							type0.getCMap().getCMapFile()), descendantID);
				}
				Set descendant = null;
				if (config.isFeatureEnabled(FeatureObjectType.FONT)) {
					descendant = new HashSet<>();
					descendant.add(descendantID);
				}
				reporter.report(GFFeaturesObjectCreator.createFontFeaturesObject(font, id, null, null, null, null, null,
						descendant, null));
			}
		} else {
			reporter.report(GFFeaturesObjectCreator.createFontFeaturesObject(font, id, null, null, null, null, null,
					null, null));
		}
	}

	private void parseColorSpace(PDColorSpace colorSpace, String id) {
		String iccProfileID = null;
		String idAlt = null;
		ASAtom colorSpaceType = colorSpace.getType();
		if (colorSpaceType == ASAtom.ICCBASED) {
			PDICCBased iccBased = (PDICCBased) colorSpace;

			ICCProfile iccProfile = iccBased.getICCProfile();
			if (iccProfile != null) {
				iccProfileID = getId(iccProfile.getObject(), FeatureObjectType.ICCPROFILE);

				if (checkIDBeforeProcess(iccProfileID)) {
					reporter.report(GFFeaturesObjectCreator
							.createICCProfileFeaturesObject(iccProfile, iccProfileID));
				}
			}

			PDColorSpace alternate = iccBased.getAlternate();
			if (alternate != null) {
				idAlt = getId(alternate.getObject(), FeatureObjectType.COLORSPACE);
				idAlt = checkColorSpaceID(idAlt, alternate);
				if (checkIDBeforeProcess(idAlt)) {
					parseColorSpace(alternate, idAlt);
				}
			}
		} else if (colorSpaceType == ASAtom.INDEXED
				|| colorSpaceType == ASAtom.SEPARATION
				|| colorSpaceType == ASAtom.DEVICEN) {

			int number = (colorSpaceType == ASAtom.INDEXED) ? 1 : 2;

			COSArray array = (COSArray) colorSpace.getObject().getDirectBase();
			COSObject base = array.at(number);
			idAlt = getId(base, FeatureObjectType.COLORSPACE);
			PDColorSpace alternate = ColorSpaceFactory.getColorSpace(base);
			if (alternate != null) {
				idAlt = checkColorSpaceID(idAlt, alternate);
				if (checkIDBeforeProcess(idAlt)) {
					parseColorSpace(alternate, idAlt);
				}
			}
		}
		iccProfileID = config.isFeatureEnabled(FeatureObjectType.ICCPROFILE) ? iccProfileID : null;
		idAlt = config.isFeatureEnabled(FeatureObjectType.COLORSPACE) ? idAlt : null;
		reporter.report(GFFeaturesObjectCreator.createColorSpaceFeaturesObject(colorSpace, id, iccProfileID, idAlt));
	}

	private static String checkColorSpaceID(String prevID, PDColorSpace colorSpace) {
		if (colorSpace != null) {
			String id = prevID;
			ASAtom colorSpaceType = colorSpace.getType();
			if (colorSpaceType == ASAtom.DEVICEGRAY) {
				id = DEVICEGRAY_ID;
			} else if (colorSpaceType == ASAtom.DEVICERGB) {
				id = DEVICERGB_ID;
			} else if (colorSpaceType == ASAtom.DEVICECMYK) {
				id = DEVICECMYK_ID;
			}
			return id;
		} else {
			return null;
		}
	}

	private String getId(final COSObject base, final FeatureObjectType objType) {
		if (base == null || base.empty()) {
			return null;
		}
		COSKey key = getObjectKey(base);
		long numb = this.processedIDs.size();
		String type = "Dir";
		if (key != null) {
			numb = key.getNumber();
			type = "Indir";
		}
		return objType.getIdPrefix() + type + numb;
	}

	private COSKey getObjectKey(final COSObject base) {
		COSKey res = null;
		if (base.isIndirect()) {
			COSObject item = base;
			while (item.isIndirect()) {
				res = item.getObjectKey();
				item = base.getDirect();
			}
		} else {
			res = base.getObjectKey();
		}
		return res;
	}

	private boolean checkIDBeforeProcess(String id) {
		if (id == null || this.processedIDs.contains(id)) {
			return false;
		}
		this.processedIDs.add(id);
		return true;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy