Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
This file is part of the iText (R) project.
Copyright (c) 1998-2023 Apryse Group NV
Authors: Apryse Software.
This program is offered under a commercial and under the AGPL license.
For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
AGPL licensing:
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
package com.itextpdf.kernel.pdf;
import com.itextpdf.io.logs.IoLogMessageConstant;
import com.itextpdf.io.font.PdfEncodings;
import com.itextpdf.commons.utils.MessageFormatUtil;
import com.itextpdf.kernel.pdf.annot.PdfAnnotation;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
final class OcgPropertiesCopier {
private static final Logger LOGGER = LoggerFactory.getLogger(OcgPropertiesCopier.class);
private OcgPropertiesCopier() {
// Empty constructor
}
public static void copyOCGProperties(PdfDocument fromDocument, PdfDocument toDocument, Map page2page) {
try {
// Configs are not copied
PdfDictionary toOcProperties = toDocument.getCatalog().getPdfObject().getAsDictionary(PdfName.OCProperties);
final Set fromOcgsToCopy = OcgPropertiesCopier
.getAllUsedNonFlushedOCGs(page2page, toOcProperties);
if (fromOcgsToCopy.isEmpty()) {
return;
}
// Reset ocProperties field in order to create it a new at the
// method end using the new (merged) OCProperties dictionary
toOcProperties = toDocument.getCatalog().fillAndGetOcPropertiesDictionary();
final PdfDictionary fromOcProperties = fromDocument.getCatalog().getPdfObject()
.getAsDictionary(PdfName.OCProperties);
OcgPropertiesCopier.copyOCGs(fromOcgsToCopy, toOcProperties, toDocument);
OcgPropertiesCopier.copyDDictionary(fromOcgsToCopy, fromOcProperties.getAsDictionary(PdfName.D),
toOcProperties, toDocument);
} catch (Exception ex) {
LOGGER.error(MessageFormatUtil.format(IoLogMessageConstant.OCG_COPYING_ERROR, ex.toString()));
}
}
private static Set getAllUsedNonFlushedOCGs(Map page2page, PdfDictionary toOcProperties) {
// NOTE: the PDF is considered to be valid and therefore the presence of OСG in OCProperties.OCGs is not checked
final Set fromUsedOcgs = new LinkedHashSet<>();
// Visit the pages in parallel to find non-flush OSGs
final PdfPage[] fromPages = page2page.keySet().toArray(new PdfPage[0]);
final PdfPage[] toPages = page2page.values().toArray(new PdfPage[0]);
for (int i = 0; i < toPages.length; i++) {
final PdfPage fromPage = fromPages[i];
final PdfPage toPage = toPages[i];
// Copy OCGs from annotations
final List toAnnotations = toPage.getAnnotations();
final List fromAnnotations = fromPage.getAnnotations();
for (int j = 0; j < toAnnotations.size(); j++) {
if (!toAnnotations.get(j).isFlushed()) {
final PdfDictionary toAnnotDict = toAnnotations.get(j).getPdfObject();
final PdfDictionary fromAnnotDict = fromAnnotations.get(j).getPdfObject();
final PdfAnnotation toAnnot = toAnnotations.get(j);
final PdfAnnotation fromAnnot = fromAnnotations.get(j);
if (!toAnnotDict.isFlushed()) {
OcgPropertiesCopier.getUsedNonFlushedOCGsFromOcDict(toAnnotDict.getAsDictionary(PdfName.OC),
fromAnnotDict.getAsDictionary(PdfName.OC), fromUsedOcgs, toOcProperties);
OcgPropertiesCopier.getUsedNonFlushedOCGsFromXObject(toAnnot.getNormalAppearanceObject(),
fromAnnot.getNormalAppearanceObject(), fromUsedOcgs, toOcProperties, new HashSet<>());
OcgPropertiesCopier.getUsedNonFlushedOCGsFromXObject(toAnnot.getRolloverAppearanceObject(),
fromAnnot.getRolloverAppearanceObject(), fromUsedOcgs, toOcProperties, new HashSet<>());
OcgPropertiesCopier.getUsedNonFlushedOCGsFromXObject(toAnnot.getDownAppearanceObject(),
fromAnnot.getDownAppearanceObject(), fromUsedOcgs, toOcProperties, new HashSet<>());
}
}
}
final PdfDictionary toResources = toPage.getPdfObject().getAsDictionary(PdfName.Resources);
final PdfDictionary fromResources = fromPage.getPdfObject().getAsDictionary(PdfName.Resources);
OcgPropertiesCopier.getUsedNonFlushedOCGsFromResources(toResources, fromResources, fromUsedOcgs,
toOcProperties, new HashSet<>());
}
return fromUsedOcgs;
}
private static void getUsedNonFlushedOCGsFromResources(PdfDictionary toResources, PdfDictionary fromResources,
Set fromUsedOcgs, PdfDictionary toOcProperties, Set visitedObjects) {
if (toResources != null && !toResources.isFlushed()) {
// Copy OCGs from properties
final PdfDictionary toProperties = toResources.getAsDictionary(PdfName.Properties);
final PdfDictionary fromProperties = fromResources.getAsDictionary(PdfName.Properties);
if (toProperties != null && !toProperties.isFlushed()) {
for (final PdfName name : toProperties.keySet()) {
final PdfObject toCurrObj = toProperties.get(name);
final PdfObject fromCurrObj = fromProperties.get(name);
OcgPropertiesCopier.getUsedNonFlushedOCGsFromOcDict(toCurrObj, fromCurrObj, fromUsedOcgs, toOcProperties);
}
}
// Copy OCGs from xObject
final PdfDictionary toXObject = toResources.getAsDictionary(PdfName.XObject);
final PdfDictionary fromXObject = fromResources.getAsDictionary(PdfName.XObject);
OcgPropertiesCopier.getUsedNonFlushedOCGsFromXObject(toXObject, fromXObject, fromUsedOcgs, toOcProperties,
visitedObjects);
}
}
private static void getUsedNonFlushedOCGsFromXObject(PdfDictionary toXObject, PdfDictionary fromXObject,
Set fromUsedOcgs, PdfDictionary toOcProperties, Set visitedObjects) {
//Resolving cycled properties, by memorizing the visited objects
if (visitedObjects.contains(fromXObject)) {
return;
}
visitedObjects.add(fromXObject);
if (toXObject != null && !toXObject.isFlushed()) {
if (toXObject.isStream() && !toXObject.isFlushed()) {
final PdfStream toStream = (PdfStream) toXObject;
final PdfStream fromStream = (PdfStream) fromXObject;
OcgPropertiesCopier.getUsedNonFlushedOCGsFromOcDict(toStream.getAsDictionary(PdfName.OC),
fromStream.getAsDictionary(PdfName.OC), fromUsedOcgs, toOcProperties);
OcgPropertiesCopier.getUsedNonFlushedOCGsFromResources(toStream.getAsDictionary(PdfName.Resources),
fromStream.getAsDictionary(PdfName.Resources), fromUsedOcgs, toOcProperties, visitedObjects);
} else {
for (final PdfName name : toXObject.keySet()) {
final PdfObject toCurrObj = toXObject.get(name);
final PdfObject fromCurrObj = fromXObject.get(name);
if (toCurrObj.isStream() && !toCurrObj.isFlushed()) {
final PdfStream toStream = (PdfStream) toCurrObj;
final PdfStream fromStream = (PdfStream) fromCurrObj;
OcgPropertiesCopier.getUsedNonFlushedOCGsFromXObject(toStream, fromStream, fromUsedOcgs,
toOcProperties, visitedObjects);
}
}
}
}
}
private static void getUsedNonFlushedOCGsFromOcDict(PdfObject toObj, PdfObject fromObj,
Set fromUsedOcgs, PdfDictionary toOcProperties) {
if (toObj != null && toObj.isDictionary() && !toObj.isFlushed()) {
final PdfDictionary toCurrDict = (PdfDictionary) toObj;
final PdfDictionary fromCurrDict = (PdfDictionary) fromObj;
final PdfName typeName = toCurrDict.getAsName(PdfName.Type);
if (PdfName.OCG.equals(typeName) && !OcgPropertiesCopier.ocgAlreadyInOCGs(toCurrDict.getIndirectReference(), toOcProperties)) {
fromUsedOcgs.add(fromCurrDict.getIndirectReference());
} else if (PdfName.OCMD.equals(typeName)) {
PdfArray toOcgs = null;
PdfArray fromOcgs = null;
if (toCurrDict.getAsDictionary(PdfName.OCGs) != null) {
toOcgs = new PdfArray();
toOcgs.add(toCurrDict.getAsDictionary(PdfName.OCGs));
fromOcgs = new PdfArray();
fromOcgs.add(fromCurrDict.getAsDictionary(PdfName.OCGs));
} else if (toCurrDict.getAsArray(PdfName.OCGs) != null) {
toOcgs = toCurrDict.getAsArray(PdfName.OCGs);
fromOcgs = fromCurrDict.getAsArray(PdfName.OCGs);
}
if (toOcgs != null && !toOcgs.isFlushed()) {
for (int i = 0; i < toOcgs.size(); i++) {
OcgPropertiesCopier.getUsedNonFlushedOCGsFromOcDict(toOcgs.get(i), fromOcgs.get(i), fromUsedOcgs, toOcProperties);
}
}
}
}
}
private static void copyOCGs(Set fromOcgsToCopy, PdfDictionary toOcProperties, PdfDocument toDocument) {
final Set layerNames = new HashSet<>();
if (toOcProperties.getAsArray(PdfName.OCGs) != null) {
final PdfArray toOcgs = toOcProperties.getAsArray(PdfName.OCGs);
for (final PdfObject toOcgObj : toOcgs) {
if (toOcgObj.isDictionary()) {
layerNames.add(((PdfDictionary) toOcgObj).getAsString(PdfName.Name).toUnicodeString());
}
}
}
boolean hasConflictingNames = false;
for (final PdfIndirectReference fromOcgRef : fromOcgsToCopy) {
final PdfDictionary toOcg = (PdfDictionary) fromOcgRef.getRefersTo().copyTo(toDocument, false);
String currentLayerName = toOcg.getAsString(PdfName.Name).toUnicodeString();
// Here we check on existed layer names only in destination document but not in source document.
// That is why there is no something like layerNames.add(currentLayerName); after this if statement
if (layerNames.contains(currentLayerName)) {
hasConflictingNames = true;
int i = 0;
while (layerNames.contains(currentLayerName + "_" + i)) {
i++;
}
currentLayerName += "_" + i;
toOcg.put(PdfName.Name, new PdfString(currentLayerName, PdfEncodings.UNICODE_BIG));
}
if (toOcProperties.getAsArray(PdfName.OCGs) == null) {
toOcProperties.put(PdfName.OCGs, new PdfArray());
}
toOcProperties.getAsArray(PdfName.OCGs).add(toOcg);
}
if (hasConflictingNames) {
LOGGER.warn(IoLogMessageConstant.DOCUMENT_HAS_CONFLICTING_OCG_NAMES);
}
}
private static boolean ocgAlreadyInOCGs(PdfIndirectReference toOcgRef, PdfDictionary toOcProperties) {
if (toOcProperties == null) {
return false;
}
final PdfArray toOcgs = toOcProperties.getAsArray(PdfName.OCGs);
if (toOcgs != null) {
for (final PdfObject toOcg : toOcgs) {
if (toOcgRef.equals(toOcg.getIndirectReference())) {
return true;
}
}
}
return false;
}
private static void copyDDictionary(Set fromOcgsToCopy, PdfDictionary fromDDict,
PdfDictionary toOcProperties, PdfDocument toDocument) {
if (toOcProperties.getAsDictionary(PdfName.D) == null) {
toOcProperties.put(PdfName.D, new PdfDictionary());
}
final PdfDictionary toDDict = toOcProperties.getAsDictionary(PdfName.D);
// The Name field is not copied because it will be given when flushing the PdfOCProperties
// Delete the Creator field because the D dictionary are changing
toDDict.remove(PdfName.Creator);
// The BaseState field is not copied because for dictionary D BaseState should have the value ON, which is the default
OcgPropertiesCopier.copyDArrayField(PdfName.ON, fromOcgsToCopy, fromDDict, toDDict, toDocument);
OcgPropertiesCopier.copyDArrayField(PdfName.OFF, fromOcgsToCopy, fromDDict, toDDict, toDocument);
// The Intent field is not copied because for dictionary D Intent should have the value View, which is the default
// The AS field is not copied because it will be given when flushing the PdfOCProperties
OcgPropertiesCopier.copyDArrayField(PdfName.Order, fromOcgsToCopy, fromDDict, toDDict, toDocument);
// The ListModel field is not copied because it only affects the visual presentation of the layers
OcgPropertiesCopier.copyDArrayField(PdfName.RBGroups, fromOcgsToCopy, fromDDict, toDDict, toDocument);
OcgPropertiesCopier.copyDArrayField(PdfName.Locked, fromOcgsToCopy, fromDDict, toDDict, toDocument);
}
private static void attemptToAddObjectToArray(Set fromOcgsToCopy, PdfObject fromObj,
PdfArray toArray, PdfDocument toDocument) {
final PdfIndirectReference fromObjRef = fromObj.getIndirectReference();
if (fromObjRef != null && fromOcgsToCopy.contains(fromObjRef)) {
toArray.add(fromObj.copyTo(toDocument, false));
}
}
private static void copyDArrayField(PdfName fieldToCopy, Set fromOcgsToCopy,
PdfDictionary fromDict, PdfDictionary toDict, PdfDocument toDocument) {
if (fromDict.getAsArray(fieldToCopy) == null) {
return;
}
final PdfArray fromArray = fromDict.getAsArray(fieldToCopy);
if (toDict.getAsArray(fieldToCopy) == null) {
toDict.put(fieldToCopy, new PdfArray());
}
final PdfArray toArray = toDict.getAsArray(fieldToCopy);
final Set toOcgsToCopy = new HashSet<>();
for (final PdfIndirectReference fromRef : fromOcgsToCopy) {
toOcgsToCopy.add(fromRef.getRefersTo().copyTo(toDocument, false).getIndirectReference());
}
if (PdfName.Order.equals(fieldToCopy)) {
// Stage 1: delete all Order the entire branches from the output document in which the copied OCGs were
final List removeIndex = new ArrayList<>();
for (int i = 0; i < toArray.size(); i++) {
PdfObject toOrderItem = toArray.get(i);
if (OcgPropertiesCopier.orderBranchContainsSetElements(toOrderItem, toArray, i, toOcgsToCopy, null, null)) {
removeIndex.add(i);
}
}
for (int i = removeIndex.size() - 1; i > -1; i--) {
toArray.remove(removeIndex.get(i));
}
final PdfArray toOcgs = toDocument.getCatalog().getPdfObject().getAsDictionary(PdfName.OCProperties).getAsArray(PdfName.OCGs);
// Stage 2: copy all the Order the entire branches in which the copied OСGs were
for (int i = 0; i < fromArray.size(); i++) {
final PdfObject fromOrderItem = fromArray.get(i);
if (OcgPropertiesCopier.orderBranchContainsSetElements(fromOrderItem, fromArray, i, fromOcgsToCopy, toOcgs, toDocument)) {
toArray.add(fromOrderItem.copyTo(toDocument, false));
}
}
// Stage 3: remove from Order OCGs not presented in the output document. When forming
// the Order dictionary in the PdfOcProperties constructor, only those OCGs that are
// in the OCProperties/OCGs array will be taken into account
} else if (PdfName.RBGroups.equals(fieldToCopy)) {
// Stage 1: delete all RBGroups from the output document in which the copied OCGs were
for (int i = toArray.size() - 1; i > -1; i--) {
final PdfArray toRbGroup = (PdfArray) toArray.get(i);
for (final PdfObject toRbGroupItemObj : toRbGroup) {
if (toOcgsToCopy.contains(toRbGroupItemObj.getIndirectReference())) {
toArray.remove(i);
break;
}
}
}
// Stage 2: copy all the RBGroups in which the copied OCGs were
for (final PdfObject fromRbGroupObj : fromArray) {
final PdfArray fromRbGroup = (PdfArray) fromRbGroupObj;
for (final PdfObject fromRbGroupItemObj : fromRbGroup) {
if (fromOcgsToCopy.contains(fromRbGroupItemObj.getIndirectReference())) {
toArray.add(fromRbGroup.copyTo(toDocument, false));
break;
}
}
}
// Stage 3: remove from RBGroups OCGs not presented in the output
// document (is in the PdfOcProperties#fillDictionary method)
} else {
for (final PdfObject fromObj : fromArray) {
OcgPropertiesCopier.attemptToAddObjectToArray(fromOcgsToCopy, fromObj, toArray, toDocument);
}
}
if (toArray.isEmpty()) {
toDict.remove(fieldToCopy);
}
}
private static boolean orderBranchContainsSetElements(PdfObject arrayObj, PdfArray array, int currentIndex,
Set ocgs, PdfArray toOcgs, PdfDocument toDocument) {
if (arrayObj.isDictionary()) {
if (ocgs.contains(arrayObj.getIndirectReference())) {
return true;
} else {
if (currentIndex < (array.size() - 1) && array.get(currentIndex + 1).isArray()) {
final PdfArray nextArray = array.getAsArray(currentIndex + 1);
if (!nextArray.get(0).isString()) {
final boolean result = OcgPropertiesCopier.orderBranchContainsSetElements(nextArray, array,
currentIndex + 1, ocgs, toOcgs, toDocument);
if (result && toOcgs != null && !ocgs.contains(arrayObj.getIndirectReference())) {
// Add the OCG to the OCGs array to register the OCG in document, since it is not used
// directly in the document, but is used as a parent for the order group. If it is not added
// to the OCGs array, then the OCG will be deleted at the 3rd stage of the /Order entry coping.
toOcgs.add(arrayObj.copyTo(toDocument, false));
}
return result;
}
}
}
} else if (arrayObj.isArray()){
final PdfArray arrayItem = (PdfArray) arrayObj;
for (int i = 0; i < arrayItem.size(); i++) {
final PdfObject obj = arrayItem.get(i);
if (OcgPropertiesCopier.orderBranchContainsSetElements(obj, arrayItem, i, ocgs, toOcgs, toDocument)) {
return true;
}
}
if (!arrayItem.isEmpty() && !arrayItem.get(0).isString()) {
if (currentIndex > 0 && array.get(currentIndex - 1).isDictionary()) {
final PdfDictionary previousDict = (PdfDictionary) array.get(currentIndex - 1);
return ocgs.contains(previousDict.getIndirectReference());
}
}
}
return false;
}
}