remove cos object key completely and test indirect reference

This commit is contained in:
Eliot Jones
2018-01-14 15:01:18 +00:00
parent 36c0eedd7c
commit 1fb6ec41d1
7 changed files with 91 additions and 486 deletions
@@ -0,0 +1,89 @@
namespace UglyToad.PdfPig.Tests.ContentStream
{
using PdfPig.ContentStream;
using Xunit;
public class IndirectReferenceTests
{
[Fact]
public void SetsProperties()
{
var reference = new IndirectReference(129, 45);
Assert.Equal(129, reference.ObjectNumber);
Assert.Equal(45, reference.Generation);
}
[Fact]
public void ToStringCorrect()
{
var reference = new IndirectReference(130, 70);
Assert.Equal("130 70", reference.ToString());
}
[Fact]
public void TwoIndirectReferenceEqual()
{
var reference1 = new IndirectReference(1574, 690);
var reference2 = new IndirectReference(1574, 690);
Assert.True(reference1.Equals(reference2));
}
[Fact]
public void TwoIndirectReferenceNotEqual()
{
var reference1 = new IndirectReference(1574, 690);
var reference2 = new IndirectReference(12, 0);
Assert.False(reference1.Equals(reference2));
}
[Fact]
public void TwoIndirectHashCodeEqual()
{
var reference1 = new IndirectReference(1267775544, 690);
var reference2 = new IndirectReference(1267775544, 690);
Assert.Equal(reference1.GetHashCode(), reference2.GetHashCode());
}
[Fact]
public void TwoIndirectHashCodeNotEqual()
{
var reference1 = new IndirectReference(1267775544, 690);
var reference2 = new IndirectReference(1267775544, 12);
Assert.NotEqual(reference1.GetHashCode(), reference2.GetHashCode());
}
[Fact]
public void TwoIndirectHashCodeSimilarValuesNotEqual()
{
var reference1 = new IndirectReference(12, 1);
var reference2 = new IndirectReference(1, 12);
Assert.NotEqual(reference1.GetHashCode(), reference2.GetHashCode());
}
[Fact]
public void OtherObjectNotEqual()
{
var reference = new IndirectReference(1267775544, 690);
var obj = "test";
// ReSharper disable once SuspiciousTypeConversion.Global
Assert.False(reference.Equals(obj));
}
[Fact]
public void NullNotEqual()
{
var reference = new IndirectReference(1267775544, 690);
// ReSharper disable once SuspiciousTypeConversion.Global
Assert.False(reference.Equals(null));
}
}
}
@@ -1,35 +0,0 @@
// ReSharper disable ObjectCreationAsStatement
namespace UglyToad.PdfPig.Tests.Cos
{
using System;
using System.Collections.Generic;
using PdfPig.Cos;
using Xunit;
public class CosObjectKeyTests
{
[Fact]
public void NullObjectThrows()
{
Action action = () => new CosObjectKey(null);
Assert.Throws<ArgumentNullException>(action);
}
[Fact]
public void CanLookupInDictionary()
{
var key1 = new CosObjectKey(3, 0);
var key2 = new CosObjectKey(3, 0);
var dictionary = new Dictionary<CosObjectKey, long>
{
{key1, 5}
};
var result = dictionary[key2];
Assert.Equal(5, result);
}
}
}
@@ -140,18 +140,7 @@
return result; return result;
} }
[CanBeNull]
public static CosObjectKey GetObjectKey(this PdfDictionary dictionary, CosName key)
{
if (!dictionary.TryGetValue(key, out var value) || !(value is CosObject obj))
{
return null;
}
return new CosObjectKey(obj);
}
[CanBeNull] [CanBeNull]
public static PdfDictionary GetDictionaryOrDefault(this PdfDictionary dictionary, public static PdfDictionary GetDictionaryOrDefault(this PdfDictionary dictionary,
CosName key) CosName key)
-5
View File
@@ -19,11 +19,6 @@
SetObject(obj); SetObject(obj);
} }
public CosObjectKey GetObjectKey()
{
return new CosObjectKey(objectNumber, generationNumber);
}
/** /**
* This will get the dictionary object in this object that has the name key and * This will get the dictionary object in this object that has the name key and
* if it is a pdfobjref then it will dereference that and return it. * if it is a pdfobjref then it will dereference that and return it.
-92
View File
@@ -1,92 +0,0 @@
namespace UglyToad.PdfPig.Cos
{
using System;
internal class CosObjectKey : IComparable<CosObjectKey>
{
public long Number { get; }
public long Generation { get; }
/**
* PDFObjectKey constructor comment.
*
* @param object The object that this key will represent.
*/
public CosObjectKey(CosObject obj) : this(GetNumber(obj), obj.GetGenerationNumber())
{
}
private static long GetNumber(CosObject obj)
{
if (obj == null)
{
throw new ArgumentNullException(nameof(obj));
}
return obj.GetObjectNumber();
}
public CosObjectKey(long num, int gen)
{
Number = num;
Generation = gen;
}
public override bool Equals(object obj)
{
CosObjectKey objToBeCompared = obj is CosObjectKey ? (CosObjectKey)obj : null;
return objToBeCompared != null &&
objToBeCompared.Number == Number &&
objToBeCompared.Generation == Generation;
}
protected bool Equals(CosObjectKey other)
{
return Number == other.Number && Generation == other.Generation;
}
public override int GetHashCode()
{
unchecked
{
// ReSharper disable once ImpureMethodCallOnReadonlyValueField
return (int)((Number.GetHashCode() * 397) ^ Generation);
}
}
public override string ToString()
{
return $"{Number} {Generation} R";
}
public int CompareTo(CosObjectKey other)
{
if (Number < other.Number)
{
return -1;
}
else if (Number > other.Number)
{
return 1;
}
else
{
if (Generation < other.Generation)
{
return -1;
}
else if (Generation > other.Generation)
{
return 1;
}
else
{
return 0;
}
}
}
}
}
+1 -8
View File
@@ -32,14 +32,7 @@
arguments.IsLenientParsing, arguments.IsLenientParsing,
requiresExistingObject); requiresExistingObject);
} }
public CosBase Parse(ParsingArguments arguments, CosObjectKey key, bool requiresExistingObject)
{
return Parse(arguments.Reader, key.Number, (int)key.Generation, arguments.CachingProviders.ObjectPool,
arguments.CrossReferenceTable, arguments.CachingProviders.BruteForceSearcher,
arguments.IsLenientParsing, requiresExistingObject);
}
public CosBase Parse(IRandomAccessRead reader, CosObject obj, CosObjectPool pool, public CosBase Parse(IRandomAccessRead reader, CosObject obj, CosObjectPool pool,
CrossReferenceTable crossReferenceTable, BruteForceSearcher bruteForceSearcher, bool isLenient, bool requireExistingObject) CrossReferenceTable crossReferenceTable, BruteForceSearcher bruteForceSearcher, bool isLenient, bool requireExistingObject)
{ {
@@ -1,334 +0,0 @@
namespace UglyToad.PdfPig.Parser
{
using System.Collections.Generic;
using System.Linq;
using Cos;
internal class XrefTrailerResolver
{
/**
* A class which represents a xref/trailer object.
*/
private class XrefTrailerObj
{
public CosDictionary trailer = null;
public XRefType xrefType;
public readonly Dictionary<CosObjectKey, long> xrefTable = new Dictionary<CosObjectKey, long>();
/**
* Default constructor.
*/
public XrefTrailerObj()
{
xrefType = XRefType.TABLE;
}
public void reset()
{
xrefTable.Clear();
}
}
/**
* The XRefType of a trailer.
*/
public enum XRefType
{
/**
* XRef table type.
*/
TABLE,
/**
* XRef stream type.
*/
STREAM
}
private readonly Dictionary<long, XrefTrailerObj> bytePosToXrefMap = new Dictionary<long, XrefTrailerObj>();
private XrefTrailerObj curXrefTrailerObj = null;
private XrefTrailerObj resolvedXrefTrailer = null;
/**
* Returns the first trailer if at least one exists.
*
* @return the first trailer or null
*/
public CosDictionary getFirstTrailer()
{
return getPositionalTrailer(Position.Last);
}
/**
* Returns the last trailer if at least one exists.
*
* @return the last trailer ir null
*/
public CosDictionary getLastTrailer()
{
return getPositionalTrailer(Position.Last);
}
private enum Position
{
First,
Last
}
private CosDictionary getPositionalTrailer(Position position)
{
if (bytePosToXrefMap.Count == 0)
{
return null;
}
var ordered = bytePosToXrefMap.Keys.OrderBy(x => x);
var key = position == Position.First ? ordered.First() : ordered.Last();
if (!bytePosToXrefMap.TryGetValue(key, out XrefTrailerObj result))
{
return null;
}
return result.trailer;
}
/**
* Returns the count of trailers.
*
* @return the count of trailers.
*/
public int getTrailerCount()
{
return bytePosToXrefMap.Count;
}
/**
* Signals that a new XRef object (table or stream) starts.
* @param startBytePos the offset to start at
* @param type the type of the Xref object
*/
public void nextXrefObj(long startBytePos, XRefType type)
{
bytePosToXrefMap[startBytePos] = curXrefTrailerObj = new XrefTrailerObj();
curXrefTrailerObj.xrefType = type;
}
/**
* Returns the XRefTxpe of the resolved trailer.
*
* @return the XRefType or null.
*/
public XRefType? getXrefType()
{
return resolvedXrefTrailer?.xrefType;
}
/**
* Populate XRef HashMap of current XRef object.
* Will add an Xreftable entry that maps ObjectKeys to byte offsets in the file.
* @param objKey The objkey, with id and gen numbers
* @param offset The byte offset in this file
*/
public void setXRef(CosObjectKey objKey, long offset)
{
if (curXrefTrailerObj == null)
{
// should not happen...
// LOG.warn("Cannot add XRef entry for '" + objKey.getNumber() + "' because XRef start was not signalled.");
return;
}
// PDFBOX-3506 check before adding to the map, to avoid entries from the table being
// overwritten by obsolete entries in hybrid files (/XRefStm entry)
if (!curXrefTrailerObj.xrefTable.ContainsKey(objKey))
{
curXrefTrailerObj.xrefTable[objKey] = offset;
}
}
/**
* Adds trailer information for current XRef object.
*
* @param trailer the current document trailer dictionary
*/
public void setTrailer(CosDictionary trailer)
{
if (curXrefTrailerObj == null)
{
// should not happen...
//LOG.warn("Cannot add trailer because XRef start was not signalled.");
return;
}
curXrefTrailerObj.trailer = trailer;
}
/**
* Returns the trailer last set by {@link #setTrailer(COSDictionary)}.
*
* @return the current trailer.
*
*/
public CosDictionary getCurrentTrailer()
{
return curXrefTrailerObj.trailer;
}
/**
* Sets the byte position of the first XRef
* (has to be called after very last startxref was read).
* This is used to resolve chain of active XRef/trailer.
*
* In case startxref position is not found we output a
* warning and use all XRef/trailer objects combined
* in byte position order.
* Thus for incomplete PDF documents with missing
* startxref one could call this method with parameter value -1.
*
* @param startxrefBytePosValue starting position of the first XRef
*
*/
public void setStartxref(long startxrefBytePosValue)
{
if (resolvedXrefTrailer != null)
{
//LOG.warn("Method must be called only ones with last startxref value.");
return;
}
resolvedXrefTrailer = new XrefTrailerObj { trailer = new CosDictionary() };
bytePosToXrefMap.TryGetValue(startxrefBytePosValue, out XrefTrailerObj curObj);
List<long> xrefSeqBytePos = new List<long>();
if (curObj == null)
{
// no XRef at given position
//LOG.warn("Did not found XRef object at specified startxref position " + startxrefBytePosValue);
// use all objects in byte position order (last entries overwrite previous ones)
xrefSeqBytePos.AddRange(bytePosToXrefMap.Keys);
xrefSeqBytePos.Sort();
}
else
{
// copy xref type
resolvedXrefTrailer.xrefType = curObj.xrefType;
// found starting Xref object
// add this and follow chain defined by 'Prev' keys
xrefSeqBytePos.Add(startxrefBytePosValue);
while (curObj.trailer != null)
{
long prevBytePos = curObj.trailer.getLong(CosName.PREV, -1L);
if (prevBytePos == -1)
{
break;
}
bytePosToXrefMap.TryGetValue(prevBytePos, out curObj);
if (curObj == null)
{
//LOG.warn("Did not found XRef object pointed to by 'Prev' key at position " + prevBytePos);
break;
}
xrefSeqBytePos.Add(prevBytePos);
// sanity check to prevent infinite loops
if (xrefSeqBytePos.Count >= bytePosToXrefMap.Count)
{
break;
}
}
// have to reverse order so that later XRefs will overwrite previous ones
xrefSeqBytePos.Reverse();
}
// merge used and sorted XRef/trailer
foreach (long bPos in xrefSeqBytePos)
{
bytePosToXrefMap.TryGetValue(bPos, out curObj);
if (curObj.trailer != null)
{
resolvedXrefTrailer.trailer.addAll(curObj.trailer);
}
foreach (var item in curObj.xrefTable)
{
resolvedXrefTrailer.xrefTable[item.Key] = item.Value;
}
}
}
/**
* Gets the resolved trailer. Might return <code>null</code> in case
* {@link #setStartxref(long)} was not called before.
*
* @return the trailer if available
*/
public CosDictionary getTrailer()
{
return (resolvedXrefTrailer == null) ? null : resolvedXrefTrailer.trailer;
}
/**
* Gets the resolved xref table. Might return <code>null</code> in case
* {@link #setStartxref(long)} was not called before.
*
* @return the xrefTable if available
*/
public Dictionary<CosObjectKey, long> getXrefTable()
{
return (resolvedXrefTrailer == null) ? null : resolvedXrefTrailer.xrefTable;
}
/** Returns object numbers which are referenced as contained
* in object stream with specified object number.
*
* This will scan resolved xref table for all entries having negated
* stream object number as value.
*
* @param objstmObjNr object number of object stream for which contained object numbers
* should be returned
*
* @return set of object numbers referenced for given object stream
* or <code>null</code> if {@link #setStartxref(long)} was not
* called before so that no resolved xref table exists
*/
public ISet<long> getContainedObjectNumbers(int objstmObjNr)
{
if (resolvedXrefTrailer == null)
{
return null;
}
ISet<long> refObjNrs = new HashSet<long>();
long cmpVal = -objstmObjNr;
foreach (var xrefEntry in resolvedXrefTrailer.xrefTable)
{
if (xrefEntry.Value == cmpVal)
{
refObjNrs.Add(xrefEntry.Key.Number);
}
}
return refObjNrs;
}
/**
* Reset all data so that it can be used to rebuild the trailer.
*
*/
public void reset()
{
foreach (XrefTrailerObj trailerObj in bytePosToXrefMap.Values)
{
trailerObj.reset();
}
curXrefTrailerObj = null;
resolvedXrefTrailer = null;
}
}
}