From 704035cfbea714bd18cb728b97bb73b37ea65d04 Mon Sep 17 00:00:00 2001 From: Maxim Date: Tue, 31 Oct 2023 12:58:52 +0300 Subject: [PATCH] PDF/UA-2. Add new annotations methods --- .../impl/containers/StaticContainers.java | 14 +++ .../model/impl/external/GFEmbeddedFile.java | 3 + .../verapdf/gf/model/impl/pd/GFPDAnnot.java | 13 ++ .../gf/model/impl/pd/GFPDDestination.java | 6 +- .../verapdf/gf/model/impl/pd/GFPDObject.java | 5 + .../impl/pd/annotations/GFPDLinkAnnot.java | 116 +++++++++++++++++- 6 files changed, 152 insertions(+), 5 deletions(-) diff --git a/validation-model/src/main/java/org/verapdf/gf/model/impl/containers/StaticContainers.java b/validation-model/src/main/java/org/verapdf/gf/model/impl/containers/StaticContainers.java index ca611da16..87057e20d 100644 --- a/validation-model/src/main/java/org/verapdf/gf/model/impl/containers/StaticContainers.java +++ b/validation-model/src/main/java/org/verapdf/gf/model/impl/containers/StaticContainers.java @@ -52,6 +52,8 @@ public class StaticContainers { private static final ThreadLocal> fileSpecificationKeys = new ThreadLocal<>(); + private static final ThreadLocal>> destinationToStructParentsMap = new ThreadLocal<>(); + private static final ThreadLocal> transparencyVisitedContentStreams = new ThreadLocal<>(); private static final ThreadLocal validPDF = new ThreadLocal<>(); @@ -75,6 +77,7 @@ public static void clearAllContainers() { cachedColorSpaces.set(new HashMap<>()); cachedFonts.set(new HashMap<>()); fileSpecificationKeys.set(new HashSet<>()); + destinationToStructParentsMap.set(new HashMap<>()); transparencyVisitedContentStreams.set(new Stack<>()); cachedGlyphs.set(new HashMap<>()); noteIDSet.set(new HashSet<>()); @@ -125,6 +128,17 @@ public static void setInconsistentSeparations(List inconsistentSeparatio StaticContainers.inconsistentSeparations.set(inconsistentSeparations); } + public static Map> getDestinationToStructParentsMap() { + if (destinationToStructParentsMap.get() == null) { + destinationToStructParentsMap.set(new HashMap<>()); + } + return destinationToStructParentsMap.get(); + } + + public static void setDestinationToStructParentsMap(Map> destinationToStructParentsMap) { + StaticContainers.destinationToStructParentsMap.set(destinationToStructParentsMap); + } + public static Map getCachedColorSpaces() { if (cachedColorSpaces.get() == null) { cachedColorSpaces.set(new HashMap<>()); diff --git a/validation-model/src/main/java/org/verapdf/gf/model/impl/external/GFEmbeddedFile.java b/validation-model/src/main/java/org/verapdf/gf/model/impl/external/GFEmbeddedFile.java index 0d0f65ab5..ca93a7b53 100644 --- a/validation-model/src/main/java/org/verapdf/gf/model/impl/external/GFEmbeddedFile.java +++ b/validation-model/src/main/java/org/verapdf/gf/model/impl/external/GFEmbeddedFile.java @@ -141,6 +141,7 @@ private static boolean isValidPdfaStream(final InputStream toValidate, final PDF private Set noteIDSet; private Set xFormKeysSet; private Set fileSpecificationKeys; + private Map> destinationToStructParentsMap; private Stack transparencyVisitedContentStreams; private Map cachedPDFonts; private Map> cachedGlyphs; @@ -164,6 +165,7 @@ private void saveStaticContainersState() { this.cachedColorSpaces = StaticContainers.getCachedColorSpaces(); this.cachedPDFonts = StaticContainers.getCachedFonts(); this.fileSpecificationKeys = StaticContainers.getFileSpecificationKeys(); + this.destinationToStructParentsMap = StaticContainers.getDestinationToStructParentsMap(); this.noteIDSet = StaticContainers.getNoteIDSet(); this.xFormKeysSet = StaticContainers.getXFormKeysSet(); this.transparencyVisitedContentStreams = StaticContainers.getTransparencyVisitedContentStreams(); @@ -190,6 +192,7 @@ private void restoreSavedSCState() { StaticContainers.setCachedColorSpaces(this.cachedColorSpaces); StaticContainers.setCachedFonts(this.cachedPDFonts); StaticContainers.setFileSpecificationKeys(this.fileSpecificationKeys); + StaticContainers.setDestinationToStructParentsMap(this.destinationToStructParentsMap); StaticContainers.setNoteIDSet(this.noteIDSet); StaticContainers.setXFormKeysSet(this.xFormKeysSet); StaticContainers.setTransparencyVisitedContentStreams(this.transparencyVisitedContentStreams); diff --git a/validation-model/src/main/java/org/verapdf/gf/model/impl/pd/GFPDAnnot.java b/validation-model/src/main/java/org/verapdf/gf/model/impl/pd/GFPDAnnot.java index 974245838..9c0e22932 100644 --- a/validation-model/src/main/java/org/verapdf/gf/model/impl/pd/GFPDAnnot.java +++ b/validation-model/src/main/java/org/verapdf/gf/model/impl/pd/GFPDAnnot.java @@ -202,6 +202,19 @@ public String getstructParentStandardType() { return null; } + @Override + public String getstructParentObjectKey() { + TaggedPDFRoleMapHelper taggedPDFRoleMapHelper = StaticResources.getRoleMapHelper(); + if (taggedPDFRoleMapHelper != null) { + COSObject parentDictionary = getParentDictionary(); + if (parentDictionary != null) { + COSKey parentKey = parentDictionary.getObjectKey(); + return parentKey != null ? parentKey.toString() : null; + } + } + return null; + } + protected COSObject getParentDictionary() { PDStructTreeRoot structTreeRoot = StaticResources.getDocument().getStructTreeRoot(); Long structParent = ((PDAnnotation) this.simplePDObject).getStructParent(); diff --git a/validation-model/src/main/java/org/verapdf/gf/model/impl/pd/GFPDDestination.java b/validation-model/src/main/java/org/verapdf/gf/model/impl/pd/GFPDDestination.java index 471e48e70..8fcb7784b 100644 --- a/validation-model/src/main/java/org/verapdf/gf/model/impl/pd/GFPDDestination.java +++ b/validation-model/src/main/java/org/verapdf/gf/model/impl/pd/GFPDDestination.java @@ -56,7 +56,8 @@ public Boolean getisStructDestination() { if (dests != null) { COSObject dest = dests.getObject(destination.getString()); if (dest == null) { - LOGGER.log(Level.WARNING, "Named destination " + destination.getString() + " not found in the Dests name tree in the Names dictionary"); + LOGGER.log(Level.WARNING, "Named destination " + destination.getString() + + " not found in the Dests name tree in the Names dictionary"); return false; } destination = dest; @@ -66,7 +67,8 @@ public Boolean getisStructDestination() { if (dests != null) { COSObject dest = dests.getKey(destination.getName()); if (dest == null) { - LOGGER.log(Level.WARNING, "Named destination " + destination.getName() + " not found in the Dests dictionary in the catalog"); + LOGGER.log(Level.WARNING, "Named destination " + destination.getName() + + " not found in the Dests dictionary in the catalog"); return false; } destination = dest; diff --git a/validation-model/src/main/java/org/verapdf/gf/model/impl/pd/GFPDObject.java b/validation-model/src/main/java/org/verapdf/gf/model/impl/pd/GFPDObject.java index 538494be3..9e8d84323 100644 --- a/validation-model/src/main/java/org/verapdf/gf/model/impl/pd/GFPDObject.java +++ b/validation-model/src/main/java/org/verapdf/gf/model/impl/pd/GFPDObject.java @@ -116,6 +116,11 @@ public String getentries() { } return ""; } + + @Override + public String getobjectKey() { + return simpleCOSObject != null && !simpleCOSObject.empty() ? simpleCOSObject.getObjectKey().toString() : null; + } @Override public String getID() { diff --git a/validation-model/src/main/java/org/verapdf/gf/model/impl/pd/annotations/GFPDLinkAnnot.java b/validation-model/src/main/java/org/verapdf/gf/model/impl/pd/annotations/GFPDLinkAnnot.java index 3dc55611b..019db24fc 100644 --- a/validation-model/src/main/java/org/verapdf/gf/model/impl/pd/annotations/GFPDLinkAnnot.java +++ b/validation-model/src/main/java/org/verapdf/gf/model/impl/pd/annotations/GFPDLinkAnnot.java @@ -21,7 +21,10 @@ package org.verapdf.gf.model.impl.pd.annotations; import org.verapdf.as.ASAtom; +import org.verapdf.cos.COSKey; +import org.verapdf.cos.COSObjType; import org.verapdf.cos.COSObject; +import org.verapdf.gf.model.impl.containers.StaticContainers; import org.verapdf.gf.model.impl.pd.GFPDAnnot; import org.verapdf.gf.model.impl.pd.GFPDDestination; import org.verapdf.gf.model.impl.pd.util.PDResourcesHandler; @@ -29,23 +32,36 @@ import org.verapdf.model.pdlayer.PDDestination; import org.verapdf.model.pdlayer.PDLinkAnnot; import org.verapdf.pd.PDAnnotation; +import org.verapdf.pd.PDNameTreeNode; +import org.verapdf.pd.PDNamesDictionary; import org.verapdf.pd.PDPage; +import org.verapdf.pd.actions.PDAction; +import org.verapdf.pdfa.flavours.PDFAFlavour; +import org.verapdf.tools.StaticResources; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; /** * @author Maxim Plushchov */ public class GFPDLinkAnnot extends GFPDAnnot implements PDLinkAnnot { + public static final Logger LOGGER = Logger.getLogger(GFPDLinkAnnot.class.getCanonicalName()); + public static final String LINK_ANNOTATION_TYPE = "PDLinkAnnot"; public static final String DEST = "Dest"; + + private String differentTargetAnnotObjectKey; + private String sameTargetAnnotObjectKey; public GFPDLinkAnnot(PDAnnotation annot, PDResourcesHandler pageResources, PDPage page) { super(annot, pageResources, page, LINK_ANNOTATION_TYPE); + if (StaticContainers.getFlavour() == PDFAFlavour.PDFUA_2) { + calculateStructDestinationProperties(); + } } @Override @@ -68,4 +84,98 @@ private List getDestination() { return Collections.emptyList(); } + private void calculateStructDestinationProperties() { + COSObject parent = getParentDictionary(); + if (parent == null || parent.getKey() == null) { + return; + } + COSKey structParentKey = parent.getKey(); + COSObject structDestination = getStructureDestinationObject(); + if (structDestination == null || structDestination.getKey() == null) { + return; + } + COSKey structDestinationKey = structDestination.getKey(); + Set structParentsSet = StaticContainers.getDestinationToStructParentsMap().computeIfAbsent(structDestinationKey, + k -> new HashSet<>()); + for (COSKey structParentObjectKey : structParentsSet) { + if (!structParentKey.equals(structParentObjectKey)) { + sameTargetAnnotObjectKey = structParentObjectKey.toString(); + break; + } + } + for (Map.Entry> entry : StaticContainers.getDestinationToStructParentsMap().entrySet()) { + if (structDestinationKey.equals(entry.getKey())) { + continue; + } + for (COSKey structParentObjectKey : entry.getValue()) { + if (structParentKey.equals(structParentObjectKey)) { + differentTargetAnnotObjectKey = structParentObjectKey.toString(); + break; + } + } + if (differentTargetAnnotObjectKey != null) { + break; + } + } + structParentsSet.add(structParentKey); + } + + private COSObject getStructureDestinationObject() { + COSObject destination = simpleCOSObject; + if (simpleCOSObject.knownKey(ASAtom.A)) { + PDAction action = ((PDAnnotation) simplePDObject).getA(); + if (ASAtom.GO_TO.equals(action.getSubtype())) { + destination = action.getDestination(); + } + } else if (simpleCOSObject.knownKey(ASAtom.DEST)) { + destination = ((PDAnnotation) simplePDObject).getDestination(); + } + if (destination == null) { + return null; + } + if (destination.getType() == COSObjType.COS_STRING) { + PDNamesDictionary namesDictionary = StaticResources.getDocument().getCatalog().getNamesDictionary(); + if (namesDictionary == null) { + return null; + } + PDNameTreeNode dests = namesDictionary.getDests(); + if (dests != null) { + COSObject dest = dests.getObject(destination.getString()); + if (dest == null) { + LOGGER.log(Level.WARNING, "Named destination " + destination.getString() + + " not found in the Dests name tree in the Names dictionary"); + return null; + } + destination = dest; + } + } else if (destination.getType() == COSObjType.COS_NAME) { + COSObject dests = StaticResources.getDocument().getCatalog().getDests(); + if (dests != null) { + COSObject dest = dests.getKey(destination.getName()); + if (dest == null) { + LOGGER.log(Level.WARNING, "Named destination " + destination.getName() + + " not found in the Dests dictionary in the catalog"); + return null; + } + destination = dest; + } + } + if (destination.getType() == COSObjType.COS_DICT) { + return destination.getKey(ASAtom.SD); + } + if (destination.getType() == COSObjType.COS_ARRAY && destination.size() > 0) { + return destination.at(0).getKey(ASAtom.S); + } + return null; + } + + @Override + public String getdifferentTargetAnnotObjectKey() { + return differentTargetAnnotObjectKey; + } + + @Override + public String getsameTargetAnnotObjectKey() { + return sameTargetAnnotObjectKey; + } }