Skip to content

Commit

Permalink
GH-233 fully working recursive document write. Need to build out some…
Browse files Browse the repository at this point in the history
… tests.
  • Loading branch information
Patrick Corless committed Jun 8, 2023
1 parent 57098fa commit 7613d01
Show file tree
Hide file tree
Showing 9 changed files with 96 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ public double getVersion() {
return version;
}

public String getWriterVersion() {
return "PDF-" + version;
}

private static double parseVersion(ByteBuffer buffer) {
byte[] versionBytes = new byte[3];
buffer.get(versionBytes);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,22 @@ public long createDocument(
OutputStream out,
long documentLength) throws IOException {

long appendedLength;
try (WritableByteChannel channel = Channels.newChannel(out)) {
if (writeMode == WriteMode.FULL_UPDATE) {
// kick of a full rewrite of the document, replacing any updates objects with new data
appendedLength = new FullUpdater().writeDocument(
long newLength = new FullUpdater().writeDocument(
document,
out);
return documentLength + appendedLength;
return newLength;
} else if (writeMode == WriteMode.INCREMENT_UPDATE) {
// copy original file data
channel.write(documentByteBuffer);
// append the data from the incremental updater
appendedLength = new IncrementalUpdater().appendIncrementalUpdate(
long appendedLength = new IncrementalUpdater().appendIncrementalUpdate(
document,
out,
documentLength);

return documentLength + appendedLength;
}
} catch (IOException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public long writeDocument(
library = catalog.getLibrary();
stateManager = library.getStateManager();
CrossReferenceRoot crossReferenceRoot = library.getCrossReferenceRoot();
PTrailer pTrailer = crossReferenceRoot.getTrailerDictionary();

SecurityManager securityManager = library.getSecurityManager();
CountingOutputStream output = new CountingOutputStream(outputStream);
Expand All @@ -43,12 +44,14 @@ public long writeDocument(
// write header
writer.writeHeader(library.getFileHeader());

// use the document root/catalog to iterate over the object tree writing out each object.
// use the document root to iterate over the object tree writing out each object.
// and keep track of each
writeDictionary(writer, catalog);
writeDictionary(writer, pTrailer);

writer.writeXRefTable();
writer.writeFullTrailer();
// writer.writeXRefTable();
// writer.writeFullTrailer();

writer.writeFullCompressedXrefTable();

}

Expand All @@ -58,7 +61,7 @@ public long writeDocument(

private void writeDictionary(BaseWriter writer, Dictionary dictionary) throws IOException {
Reference dictionaryReference = dictionary.getPObjectReference();
if (writer.hasNotWrittenReference(dictionaryReference)) {
if (dictionaryReference != null && writer.hasNotWrittenReference(dictionaryReference)) {
StateManager.Change change = stateManager.getChange(dictionaryReference);
if (change != null) {
if (change.getType() == StateManager.Type.CHANGE) {
Expand All @@ -69,49 +72,46 @@ private void writeDictionary(BaseWriter writer, Dictionary dictionary) throws IO
}
}
DictionaryEntries entries = dictionary.getEntries();
findChildDictionaries(writer, entries);
writeDictionaryEntries(writer, entries);
}

private void findChildDictionaries(BaseWriter writer, DictionaryEntries entries) throws IOException {
private void writePObject(BaseWriter writer, Object object) throws IOException {
if (object instanceof Reference && writer.hasNotWrittenReference((Reference) object)) {
Object objectReferenceValue = library.getObject(object);
StateManager.Change change = stateManager.getChange((Reference) object);
if (change != null) {
if (change.getType() == StateManager.Type.CHANGE) {
writer.writePObject(change.getPObject());
}
} else {
writer.writePObject(new PObject(objectReferenceValue, (Reference) object));
}
if (objectReferenceValue instanceof Dictionary) {
writeDictionary(writer, (Dictionary) objectReferenceValue);
} else if (objectReferenceValue instanceof DictionaryEntries) {
writeDictionaryEntries(writer, (DictionaryEntries) objectReferenceValue);
} else if (objectReferenceValue instanceof List) {
writeList(writer, (List) objectReferenceValue);
}
}
}

private void writeDictionaryEntries(BaseWriter writer, DictionaryEntries entries) throws IOException {
for (Name name : entries.keySet()) {
Object value = entries.get(name);
if (value instanceof Reference && writer.hasNotWrittenReference((Reference) value)) {
Object object = library.getObject(value);
StateManager.Change change = stateManager.getChange((Reference) value);
if (change != null) {
if (change.getType() == StateManager.Type.CHANGE) {
writer.writePObject(change.getPObject());
}
} else {
writer.writePObject(new PObject(object, (Reference) value));
}
if (object instanceof Dictionary) {
writeDictionary(writer, (Dictionary) object);
} else if (object instanceof DictionaryEntries) {
findChildDictionaries(writer, (DictionaryEntries) object);
}
writePObject(writer, value);
} else if (value instanceof List) {
for (Object object : (List) value) {
if (object instanceof Reference && writer.hasNotWrittenReference((Reference) object)) {
Object objectReferenceValue = library.getObject(object);
StateManager.Change change = stateManager.getChange((Reference) object);
if (change != null) {
if (change.getType() == StateManager.Type.CHANGE) {
writer.writePObject(change.getPObject());
}
} else {
writer.writePObject(new PObject(objectReferenceValue, (Reference) object));
}
if (objectReferenceValue instanceof Dictionary) {
writeDictionary(writer, (Dictionary) objectReferenceValue);
} else if (objectReferenceValue instanceof DictionaryEntries) {
findChildDictionaries(writer, (DictionaryEntries) objectReferenceValue);
}
}
}
writeList(writer, (List) value);
} else if (value instanceof DictionaryEntries) {
findChildDictionaries(writer, (DictionaryEntries) value);
writeDictionaryEntries(writer, (DictionaryEntries) value);
}
}
}

private void writeList(BaseWriter writer, List values) throws IOException {
for (Object object : values) {
writePObject(writer, object);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public long appendIncrementalUpdate(
// todo may need updating as I don't think it handles hybrid mode
PTrailer trailer = crossReferenceRoot.getTrailerDictionary();
if (trailer.isCompressedXref()) {
writer.writeCompressedXrefTable();
writer.writeIncrementalCompressedXrefTable();
} else {
writer.writeXRefTable();
writer.writeIncrementalUpdateTrailer();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class BaseTableWriter extends BaseWriter {
protected static final byte[] XREF = "xref\r\n".getBytes();
protected static final byte[] TRAILER = "trailer\r\n".getBytes();
protected static final byte[] STARTXREF = "startxref\r\n".getBytes();
protected static final byte[] COMMENT_EOF = "%%EOF\r\n".getBytes();
protected static final byte[] COMMENT_EOF = "\r\n%%EOF\r\n".getBytes();

protected int subSectionCount(int beginIndex, List<Entry> entries) {
int beginObjNum = entries.get(beginIndex).getReference().getObjectNumber();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,12 @@ public void writeFullTrailer() throws IOException {
trailerWriter.writeFullTrailer(crossReferenceRoot, xrefPosition, entries, output);
}

public void writeCompressedXrefTable() throws IOException {
compressedXrefTableWriter.writeCompressedXrefTable(crossReferenceRoot, securityManager, entries, startingPosition, output);
public void writeIncrementalCompressedXrefTable() throws IOException {
compressedXrefTableWriter.writeIncrementalCompressedXrefTable(crossReferenceRoot, securityManager, entries, startingPosition, output);
}

public void writeFullCompressedXrefTable() throws IOException {
compressedXrefTableWriter.writeFullCompressedXrefTable(crossReferenceRoot, securityManager, entries, startingPosition, output);
}

public void writeNewLine() throws IOException {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package org.icepdf.core.util.updater.writeables;

import org.icepdf.core.io.CountingOutputStream;
import org.icepdf.core.pobjects.DictionaryEntries;
import org.icepdf.core.pobjects.PTrailer;
import org.icepdf.core.pobjects.Reference;
import org.icepdf.core.pobjects.Stream;
import org.icepdf.core.pobjects.*;
import org.icepdf.core.pobjects.security.SecurityManager;
import org.icepdf.core.pobjects.structure.CrossReferenceEntry;
import org.icepdf.core.pobjects.structure.CrossReferenceRoot;
import org.icepdf.core.pobjects.structure.CrossReferenceStream;
import org.icepdf.core.util.Utils;

import java.io.ByteArrayOutputStream;
Expand All @@ -20,8 +18,9 @@ public class CompressedXrefTableWriter extends BaseTableWriter {

private static final List<Integer> WIDTHS = Arrays.asList(4, 8, 4);

public void writeCompressedXrefTable(CrossReferenceRoot crossReferenceRoot, SecurityManager securityManager, List<Entry> entries,
long startingPosition, CountingOutputStream output) throws IOException {
public void writeIncrementalCompressedXrefTable(CrossReferenceRoot crossReferenceRoot,
SecurityManager securityManager, List<Entry> entries,
long startingPosition, CountingOutputStream output) throws IOException {
PTrailer prevTrailer = crossReferenceRoot.getTrailerDictionary();
DictionaryEntries newTrailer = (DictionaryEntries) prevTrailer.getDictionary().clone();
this.setPreviousTrailer(newTrailer, crossReferenceRoot);
Expand All @@ -30,6 +29,25 @@ public void writeCompressedXrefTable(CrossReferenceRoot crossReferenceRoot, Secu
newTrailer.remove(Stream.DECODEPARAM_KEY);
newTrailer.put(Stream.FILTER_KEY, Stream.FILTER_FLATE_DECODE);

writeCompressedXrefTable(securityManager, newTrailer, entries, newTrailerSize, xrefPos, output);
}

public void writeFullCompressedXrefTable(CrossReferenceRoot crossReferenceRoot,
SecurityManager securityManager, List<Entry> entries,
long startingPosition, CountingOutputStream output) throws IOException {
PTrailer prevTrailer = crossReferenceRoot.getTrailerDictionary();
DictionaryEntries newTrailer = (DictionaryEntries) prevTrailer.getDictionary().clone();
int newTrailerSize = entries.size();
newTrailer.put(PTrailer.SIZE_KEY, newTrailerSize);
long xrefPos = output.getCount();
newTrailer.remove(Stream.DECODEPARAM_KEY);
newTrailer.put(Stream.FILTER_KEY, Stream.FILTER_FLATE_DECODE);
writeCompressedXrefTable(securityManager, newTrailer, entries, newTrailerSize, xrefPos, output);
}

private void writeCompressedXrefTable(SecurityManager securityManager, DictionaryEntries newTrailer,
List<Entry> entries, int newTrailerSize, long xrefPos,
CountingOutputStream output) throws IOException {
this.closeTableEntries(entries);

newTrailer.put(PTrailer.W_KEY, WIDTHS);
Expand All @@ -40,6 +58,8 @@ public void writeCompressedXrefTable(CrossReferenceRoot crossReferenceRoot, Secu
}
newTrailer.put(PTrailer.INDEX_KEY, index);

newTrailer.put(Dictionary.TYPE_KEY, CrossReferenceStream.TYPE);

Stream crossReferenceStream = new Stream(newTrailer, new byte[0]);
crossReferenceStream.setPObjectReference(new Reference(newTrailerSize, 0));
byte[] outputData = createXrefDataStream(entries);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class HeaderWriter extends BaseWriter {

public void write(Header header, CountingOutputStream output) throws IOException {
output.write(commentMarker);
writeReal(header.getVersion(), output);
output.write(header.getWriterVersion().getBytes());
output.write(NEWLINE);
output.write(commentMarker);
output.write(FOUR_BYTES);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.*;

public class ObjectUpdateTests {

Expand All @@ -22,15 +20,22 @@ public void testXrefTableFullUpdate() {
InputStream fileUrl = ObjectUpdateTests.class.getResourceAsStream("/updater/R&D-05-Carbon.pdf");
document.setInputStream(fileUrl, "R&D-05-Carbon.pdf");

Page page = document.getPageTree().getPage(0);
Page page = document.getPageTree().getPage(2);
document.removePage(page);
FileOutputStream fileOutputStream = new FileOutputStream("./src/test/out/ObjectUpdateTest.pdf");
document.saveToOutputStream(fileOutputStream, WriteMode.FULL_UPDATE);

File out = new File("./src/test/out/ObjectUpdateTest.pdf");

BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(out), 8192);

long length = document.saveToOutputStream(stream, WriteMode.FULL_UPDATE);

System.out.println("length: " + length);

// check file length
fileOutputStream.close();
stream.close();

// open the output and check for the missing objcts
// open the output and check for the removed objects
OutputStreamWriter test = new OutputStreamWriter(stream);


} catch (PDFSecurityException e) {
Expand Down

0 comments on commit 7613d01

Please sign in to comment.