Skip to content

Commit 72c2769

Browse files
Improve free references handling, fix their representation in xref table
DEVSIX-608
1 parent 2b141be commit 72c2769

File tree

8 files changed

+274
-125
lines changed

8 files changed

+274
-125
lines changed

io/src/main/java/com/itextpdf/io/LogMessageConstant.java

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -49,57 +49,61 @@ This file is part of the iText (R) project.
4949
public final class LogMessageConstant {
5050

5151
public static final String ACTION_WAS_SET_TO_LINK_ANNOTATION_WITH_DESTINATION = "Action was set for a link annotation containing destination. The old destination will be cleared.";
52+
public static final String ALREADY_FLUSHED_INDIRECT_OBJECT_MADE_FREE = "An attempt is made to free already flushed indirect object reference. Indirect reference wasn't freed.";
5253
public static final String ATTEMPT_TO_MOVE_TO_FLUSHED_PARENT = "An attempt is made to move the tag tree pointer to the tag parent which has been already flushed. Tag tree pointer is moved to the root tag instead.";
54+
public static final String CALCULATE_HASHCODE_FOR_MODIFIED_PDFNUMBER = "Calculate hashcode for modified PdfNumber.";
5355
public static final String CLIP_ELEMENT = "Element content was clipped because some height properties are set.";
56+
public static final String COLORANT_INTENSITIES_INVALID = "Some of colorant intensities are invalid: they are bigger than 1 or less than 0. We will force them to become 1 or 0 respectively.";
5457
public static final String COLOR_NOT_FOUND = "Color \"{0}\" not found.";
5558
public static final String COLOR_NOT_PARSED = "Color \"{0}\" was not parsed. It has invalid value. Defaulting to black color.";
56-
public static final String COLORANT_INTENSITIES_INVALID = "Some of colorant intensities are invalid: they are bigger than 1 or less than 0. We will force them to become 1 or 0 respectively.";
5759
public static final String COULD_NOT_FIND_GLYPH_WITH_CODE = "Could not find glyph with the following code: {0}";
5860
public static final String DESTINATION_NOT_PERMITTED_WHEN_ACTION_IS_SET = "Destinations are not permitted for link annotations that already have actions. The old action will be removed.";
59-
public static final String DOCUMENT_ALREADY_HAS_FIELD = "The document already has field {0}. Annotations of the fields with this name will be added to the existing one as children. If you want to have separate fields, please, rename them manually before copying.";
60-
public static final String DOCUMENT_SERIALIZATION_EXCEPTION_RAISED = "Unhandled exception while serialization";
6161
public static final String DIRECTONLY_OBJECT_CANNOT_BE_INDIRECT = "DirectOnly object cannot be indirect";
6262
public static final String DOCFONT_HAS_ILLEGAL_DIFFERENCES = "Document Font has illegal differences array. Entry {0} references a glyph ID over 255 and will be ignored.";
63-
public static final String GRAPHICS_STATE_WAS_DELETED = "Graphics state is always deleted after event dispatching. If you want to preserve it in renderer info, use preserveGraphicsState method after receiving renderer info.";
63+
public static final String DOCUMENT_ALREADY_HAS_FIELD = "The document already has field {0}. Annotations of the fields with this name will be added to the existing one as children. If you want to have separate fields, please, rename them manually before copying.";
64+
public static final String DOCUMENT_SERIALIZATION_EXCEPTION_RAISED = "Unhandled exception while serialization";
6465
public static final String ELEMENT_DOES_NOT_FIT_AREA = "Element does not fit current area. {0}";
6566
public static final String ELEMENT_WAS_FORCE_PLACED_KEEP_WITH_NEXT_WILL_BE_IGNORED = "Element was placed in a forced way. Keep with next property will be ignored";
6667
public static final String ENCOUNTERED_INVALID_MCR = "Corrupted tag structure: encountered invalid marked content reference - it doesn't refer to any page or any mcid. This content reference will be ignored.";
67-
public static final String EXCEPTION_WHILE_UPDATING_XMPMETADATA = "Exception while updating XmpMetadata";
6868
public static final String EXCEPTION_WHILE_CREATING_DEFAULT_FONT = "Exception while creating default font (Helvetica, WinAnsi)";
69+
public static final String EXCEPTION_WHILE_UPDATING_XMPMETADATA = "Exception while updating XmpMetadata";
6970
public static final String FILE_CHANNEL_CLOSING_FAILED = "Closing of the file channel this source is based on failed.";
71+
public static final String FLUSHED_OBJECT_CONTAINS_REFERENCE_WHICH_NOT_REFER_TO_ANY_OBJECT = "Flushed object contains indirect reference which doesn't refer to any other object (e.g. this reference might be a free reference). Null object will be written instead.";
7072
public static final String FONT_HAS_INVALID_GLYPH = "Font {0} has invalid glyph: {1}";
71-
public static final String FORBID_RELEASE_IS_SET = "ForbidRelease flag is set and release is called. Releasing will not be performed.";
7273
public static final String FONT_PROPERTY_MUST_BE_PDF_FONT_OBJECT = "The Font Property must be a PdfFont object";
74+
public static final String FORBID_RELEASE_IS_SET = "ForbidRelease flag is set and release is called. Releasing will not be performed.";
7375
public static final String FORM_FIELD_WAS_FLUSHED = "A form field was flushed. There's no way to create this field in the AcroForm dictionary.";
76+
public static final String GRAPHICS_STATE_WAS_DELETED = "Graphics state is always deleted after event dispatching. If you want to preserve it in renderer info, use preserveGraphicsState method after receiving renderer info.";
7477
public static final String IMAGE_HAS_AMBIGUOUS_SCALE = "The image cannot be auto scaled and scaled by a certain parameter simultaneously";
7578
public static final String IMAGE_HAS_JBIG2DECODE_FILTER = "Image cannot be inline if it has JBIG2Decode filter. It will be added as an ImageXObject";
76-
public static final String IMAGE_HAS_MASK = "Image cannot be inline if it has a Mask";
7779
public static final String IMAGE_HAS_JPXDECODE_FILTER = "Image cannot be inline if it has JPXDecode filter. It will be added as an ImageXObject";
80+
public static final String IMAGE_HAS_MASK = "Image cannot be inline if it has a Mask";
7881
public static final String IMAGE_SIZE_CANNOT_BE_MORE_4KB = "Inline image size cannot be more than 4KB. It will be added as an ImageXObject";
7982
public static final String INCORRECT_PAGEROTATION = "Encounterd a page rotation that was not a multiple of 90°/ (Pi/2) when generating default appearances for form fields";
83+
public static final String INDIRECT_REFERENCE_USED_IN_FLUSHED_OBJECT_MADE_FREE = "An attempt is made to free an indirect reference which was already used in the flushed object. Indirect reference wasn't freed.";
8084
public static final String INLINE_BLOCK_ELEMENT_WILL_BE_CLIPPED = "Inline block element does not fit into parent element and will be clipped";
8185
public static final String INPUT_STREAM_CONTENT_IS_LOST_ON_PDFSTREAM_SERIALIZATION = "PdfStream contains not null input stream. It's content will be lost in serialized object.";
8286
public static final String INVALID_INDIRECT_REFERENCE = "Invalid indirect reference {0} {1} R";
8387
public static final String INVALID_KEY_VALUE_KEY_0_HAS_NULL_VALUE = "Invalid key value: key {0} has null value.";
8488
public static final String LAST_ROW_IS_NOT_COMPLETE = "Last row is not completed. Table bottom border may collapse as you do not expect it";
85-
public static final String CALCULATE_HASHCODE_FOR_MODIFIED_PDFNUMBER = "Calculate hashcode for modified PdfNumber.";
8689
public static final String MAKE_COPY_OF_CATALOG_DICTIONARY_IS_FORBIDDEN = "Make copy of Catalog dictionary is forbidden.";
8790
public static final String NAME_ALREADY_EXISTS_IN_THE_NAME_TREE = "Name \"{0}\" already exists in the name tree; old value will be replaced by the new one.";
88-
public static final String NO_FIELDS_IN_ACROFORM = "Required AcroForm entry /Fields does not exist in the document. Empty array /Fields will be created.";
8991
public static final String NOT_TAGGED_PAGES_IN_TAGGED_DOCUMENT = "Not tagged pages are copied to the tagged document. Destination document now may contain not tagged content.";
92+
public static final String NO_FIELDS_IN_ACROFORM = "Required AcroForm entry /Fields does not exist in the document. Empty array /Fields will be created.";
9093
public static final String NUM_TREE_SHALL_NOT_END_WITH_KEY = "Number tree ends with a key which is invalid according to the PDF specification.";
91-
public static final String PDF_READER_CLOSING_FAILED = "PdfReader closing failed due to the error occurred!";
92-
public static final String PDF_WRITER_CLOSING_FAILED = "PdfWriter closing failed due to the error occurred!";
93-
public static final String POPUP_ENTRY_IS_NOT_POPUP_ANNOTATION = "Popup entry in the markup annotations refers not to the annotation with Popup subtype.";
9494
public static final String OCCUPIED_AREA_HAS_NOT_BEEN_INITIALIZED = "Occupied area has not been initialized. {0}";
9595
public static final String OCSP_STATUS_IS_REVOKED = "OCSP status is revoked.";
9696
public static final String OCSP_STATUS_IS_UNKNOWN = "OCSP status is unknown.";
9797
public static final String ONE_OF_GROUPED_SOURCES_CLOSING_FAILED = "Closing of one of the grouped sources failed.";
9898
public static final String ONLY_ONE_OF_ARTBOX_OR_TRIMBOX_CAN_EXIST_IN_THE_PAGE = "Only one of artbox or trimbox can exist on the page. The trimbox will be deleted";
99+
public static final String OPENTYPE_GDEF_TABLE_ERROR = "OpenType GDEF table error: {0}";
99100
public static final String PDF_OBJECT_FLUSHING_NOT_PERFORMED = "PdfObject flushing is not performed: PdfDocument is opened in append mode and the object is not marked as modified ( see PdfObject#setModified() ).";
101+
public static final String PDF_READER_CLOSING_FAILED = "PdfReader closing failed due to the error occurred!";
102+
public static final String PDF_WRITER_CLOSING_FAILED = "PdfWriter closing failed due to the error occurred!";
103+
public static final String POPUP_ENTRY_IS_NOT_POPUP_ANNOTATION = "Popup entry in the markup annotations refers not to the annotation with Popup subtype.";
100104
public static final String PROPERTY_IN_PERCENTS_NOT_SUPPORTED = "Property {0} in percents is not supported";
101-
public static final String RECTANGLE_HAS_NEGATIVE_SIZE = "The {0} rectangle has negative size. It will not be displayed.";
102105
public static final String RECTANGLE_HAS_NEGATIVE_OR_ZERO_SIZES = "The {0} rectangle has negative or zero sizes. It will not be displayed.";
106+
public static final String RECTANGLE_HAS_NEGATIVE_SIZE = "The {0} rectangle has negative size. It will not be displayed.";
103107
public static final String REGISTERING_DIRECTORY = "Registering directory";
104108
public static final String REMOVING_PAGE_HAS_ALREADY_BEEN_FLUSHED = "The removing page has already been flushed.";
105109
public static final String RENDERER_WAS_NOT_ABLE_TO_PROCESS_KEEP_WITH_NEXT = "The renderer was not able to process keep with next property properly";
@@ -110,13 +114,12 @@ public final class LogMessageConstant {
110114
public static final String TABLE_WIDTH_IS_MORE_THAN_EXPECTED_DUE_TO_MIN_WIDTH = "Table width is more than expected due to min width of cell(s).";
111115
public static final String TAG_STRUCTURE_CONTEXT_WILL_BE_REINITIALIZED_ON_SERIALIZATION = "Tag structure context is not null and will be reinitialized in the copy of document. The copy may lose some data";
112116
public static final String TAG_STRUCTURE_INIT_FAILED = "Tag structure initialization failed, tag structure is ignored, it might be corrupted.";
117+
public static final String TOUNICODE_CMAP_MORE_THAN_2_BYTES_NOT_SUPPORTED = "ToUnicode CMap more than 2 bytes not supported.";
113118
public static final String UNEXPECTED_BEHAVIOUR_DURING_TABLE_ROW_COLLAPSING = "Unexpected behaviour during table row collapsing. Calculated rowspan was less then 1.";
114119
public static final String UNKNOWN_CMAP = "Unknown CMap {0}";
115120
public static final String UNKNOWN_COLOR_FORMAT_MUST_BE_RGB_OR_RRGGBB = "Unknown color format: must be rgb or rrggbb.";
116121
public static final String UNKNOWN_ERROR_WHILE_PROCESSING_CMAP = "Unknown error while processing CMap.";
117-
public static final String TOUNICODE_CMAP_MORE_THAN_2_BYTES_NOT_SUPPORTED = "ToUnicode CMap more than 2 bytes not supported.";
118122
public static final String WRITER_ENCRYPTION_IS_IGNORED_APPEND = "Writer encryption will be ignored, because append mode is used. Document will preserve the original encryption (or will stay unencrypted)";
119123
public static final String WRITER_ENCRYPTION_IS_IGNORED_PRESERVE = "Writer encryption will be ignored, because preservation of encryption is enabled. Document will preserve the original encryption (or will stay unencrypted)";
120124
public static final String XREF_ERROR = "Error occurred while reading cross reference table. Cross reference table will be rebuilt.";
121-
public static final String OPENTYPE_GDEF_TABLE_ERROR = "OpenType GDEF table error: {0}";
122125
}

kernel/src/main/java/com/itextpdf/kernel/pdf/PdfDocument.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,13 @@ public void close() {
770770
flushFonts();
771771

772772
writer.flushModifiedWaitingObjects();
773+
for (int i = 0; i < xref.size(); i++) {
774+
PdfIndirectReference indirectReference = xref.get(i);
775+
if (indirectReference != null && !indirectReference.isFree()
776+
&& indirectReference.checkState(PdfObject.MODIFIED) && !indirectReference.checkState(PdfObject.FLUSHED)) {
777+
indirectReference.setFree();
778+
}
779+
}
773780
if (writer.crypto != null) {
774781
assert reader.decrypt.getPdfObject() == writer.crypto.getPdfObject() : "Conflict with source encryption";
775782
crypto = reader.decrypt.getPdfObject();
@@ -803,12 +810,11 @@ public void close() {
803810
info.flush();
804811
flushFonts();
805812
writer.flushWaitingObjects();
806-
// flush unused objects
807813
for (int i = 0; i < xref.size(); i++) {
808814
PdfIndirectReference indirectReference = xref.get(i);
809815
if (indirectReference != null && !indirectReference.isFree() && !indirectReference.checkState(PdfObject.FLUSHED)) {
810-
if (isFlushUnusedObjects() && !indirectReference.checkState(PdfObject.ORIGINAL_OBJECT_STREAM)) {
811-
PdfObject object = indirectReference.getRefersTo();
816+
PdfObject object;
817+
if (isFlushUnusedObjects() && !indirectReference.checkState(PdfObject.ORIGINAL_OBJECT_STREAM) && (object = indirectReference.getRefersTo(false)) != null) {
812818
object.flush();
813819
} else {
814820
indirectReference.setFree();
@@ -1670,7 +1676,7 @@ protected void open(PdfVersion newPdfVersion) {
16701676
} catch (XMPException ignored) {
16711677
}
16721678
}
1673-
PdfObject infoDict = trailer.get(PdfName.Info, true);
1679+
PdfObject infoDict = trailer.get(PdfName.Info);
16741680
info = new PdfDocumentInfo(infoDict instanceof PdfDictionary ?
16751681
(PdfDictionary) infoDict : new PdfDictionary(), this);
16761682

@@ -1681,6 +1687,7 @@ protected void open(PdfVersion newPdfVersion) {
16811687
if (properties.appendMode && (reader.hasRebuiltXref() || reader.hasFixedXref()))
16821688
throw new PdfException(PdfException.AppendModeRequiresADocumentWithoutErrorsEvenIfRecoveryWasPossible);
16831689
}
1690+
xref.initFreeReferencesList(this);
16841691
if (writer != null) {
16851692
if (reader != null && reader.hasXrefStm() && writer.properties.isFullCompression == null) {
16861693
writer.properties.isFullCompression = true;

kernel/src/main/java/com/itextpdf/kernel/pdf/PdfIndirectReference.java

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ public PdfObject getRefersTo() {
123123
*/
124124
public PdfObject getRefersTo(boolean recursively) {
125125
if (!recursively) {
126-
if (refersTo == null && !checkState(FLUSHED) && !checkState(MODIFIED) && getReader() != null) {
126+
if (refersTo == null && !checkState(FLUSHED) && !checkState(MODIFIED) && !checkState(FREE) && getReader() != null) {
127127
refersTo = getReader().readObject(this);
128128
}
129129
return refersTo;
@@ -202,17 +202,34 @@ public PdfDocument getDocument() {
202202
}
203203

204204
/**
205-
* Releases indirect reference from the document. Remove link to the referenced indirect object.
205+
* Marks indirect reference as free in the document. This doesn't "remove" indirect objects from the document,
206+
* it only ensures that corresponding xref entry is free and indirect object referred by this reference is no longer
207+
* linked to it. Actual object still might be written to the resultant document (and would get a new corresponding
208+
* indirect reference in this case) if it is still contained in some other object.
206209
* <p>
207-
* Note: Be careful when using this method. Do not use this method for wrapper objects,
208-
* it can be cause of errors.
209-
* Free indirect reference could be reused for a new indirect object.
210+
* This method will not give any result if the corresponding indirect object or another object
211+
* that contains a reference to this object is already flushed.
212+
* </p>
213+
* <p>
214+
* Note: in some cases, removing a link of indirect object to it's indirect reference while
215+
* leaving the actual object in the document structure might lead to errors, because some objects are expected
216+
* to always have such explicit link (e.g. Catalog object, page objects, etc).
210217
* </p>
211218
*/
212219
public void setFree() {
213220
getDocument().getXref().freeReference(this);
214221
}
215222

223+
/**
224+
* Checks if this {@link PdfIndirectReference} instance corresponds to free indirect reference.
225+
* Indirect reference might be in a free state either because it was read as such from the opened existing
226+
* PDF document or because it was set free via {@link PdfIndirectReference#setFree()} method.
227+
* @return {@code true} if this {@link PdfIndirectReference} is free, {@code false} otherwise.
228+
*/
229+
public boolean isFree() {
230+
return checkState(FREE);
231+
}
232+
216233
@Override
217234
public String toString() {
218235
StringBuilder states = new StringBuilder(" ");
@@ -265,10 +282,6 @@ protected PdfReader getReader() {
265282
return null;
266283
}
267284

268-
protected boolean isFree() {
269-
return checkState(FREE);
270-
}
271-
272285
@Override
273286
protected PdfObject newInstance() {
274287
return PdfNull.PDF_NULL;
@@ -300,7 +313,7 @@ void setOffset(long offset) {
300313
this.objectStreamNumber = 0;
301314
}
302315

303-
void fixOffset(long offset){
316+
void fixOffset(long offset) {
304317
if (!isFree()) {
305318
this.offsetOrIndex = offset;
306319
}

kernel/src/main/java/com/itextpdf/kernel/pdf/PdfNull.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,7 @@ This file is part of the iText (R) project.
4343
*/
4444
package com.itextpdf.kernel.pdf;
4545

46-
import com.itextpdf.io.LogMessageConstant;
4746
import com.itextpdf.io.source.ByteUtils;
48-
import org.slf4j.Logger;
49-
import org.slf4j.LoggerFactory;
5047

5148
/**
5249
* Representation of the null object in the PDF specification.

kernel/src/main/java/com/itextpdf/kernel/pdf/PdfOutputStream.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,8 @@ private void write(PdfIndirectReference indirectReference) {
192192
throw new PdfException(PdfException.PdfIndirectObjectBelongsToOtherPdfDocument);
193193
}
194194
if (indirectReference.getRefersTo() == null) {
195+
Logger logger = LoggerFactory.getLogger(PdfOutputStream.class);
196+
logger.error(LogMessageConstant.FLUSHED_OBJECT_CONTAINS_REFERENCE_WHICH_NOT_REFER_TO_ANY_OBJECT);
195197
write(PdfNull.PDF_NULL);
196198
} else if (indirectReference.getGenNumber() == 0) {
197199
writeInteger(indirectReference.getObjNumber()).

0 commit comments

Comments
 (0)