PDFページの抽出とマージにpyPDFを使用しています。過去に同じpdfファイルでpdfSharpで同じタイプのエラーに遭遇したため、私の問題はpyPDFに完全に依存しているわけではありません。
問題は、ベンダーから受け取った一部の PDF ドキュメントを読み込もうとするとエラーが発生することです。私は彼らに直せとは言えませんので、こちらで対処しなければなりません。現在、私はJavaでiTextを使用してPDFのマージを処理していますが、これらのファイルには問題はありませんが、iTextはpyPDFよりも遅く、維持が困難です。pyPDF には、xref テーブルを読み取るためのセクションがあります。そのセクションには、「xref」で始まる行、数字で始まる行、または「xref」で始まる行のいずれかのオプションがいくつかありますが、x の前に余分な文字がありました。
私の場合、行は「196 0 obj」で始まりますが、次の行は「<< /Length 197 0 R」です。pyPDF と pdfSharp はそれを認識せず、それを相互参照として読み取って例外をスローしようとしています。これを回避するため、またはpyPDFにパッチを当てるために私ができることについて何か提案はありますか? 形式が悪いかもしれませんが、Acrobat や iText と同じように回避する必要があります。
これは、pyPDF ライブラリの pdf.py のセクションです。これは多くのコードですが、重要なビットは、 if x == "x"で始まる一連の if ステートメントです。
# read all cross reference tables and their trailers
self.xref = {}
self.xref_objStm = {}
self.trailer = DictionaryObject()
while 1:
# load the xref table
stream.seek(startxref, 0)
x = stream.read(1)
if x == "x":
# standard cross-reference table
ref = stream.read(4)
if ref[:3] != "ref":
raise utils.PdfReadError, "xref table read error"
readNonWhitespace(stream)
stream.seek(-1, 1)
while 1:
num = readObject(stream, self)
readNonWhitespace(stream)
stream.seek(-1, 1)
size = readObject(stream, self)
readNonWhitespace(stream)
stream.seek(-1, 1)
cnt = 0
while cnt < size:
line = stream.read(20)
# It's very clear in section 3.4.3 of the PDF spec
# that all cross-reference table lines are a fixed
# 20 bytes. However... some malformed PDF files
# use a single character EOL without a preceeding
# space. Detect that case, and seek the stream
# back one character. (0-9 means we've bled into
# the next xref entry, t means we've bled into the
# text "trailer"):
if line[-1] in "0123456789t":
stream.seek(-1, 1)
offset, generation = line[:16].split(" ")
offset, generation = int(offset), int(generation)
if not self.xref.has_key(generation):
self.xref[generation] = {}
if self.xref[generation].has_key(num):
# It really seems like we should allow the last
# xref table in the file to override previous
# ones. Since we read the file backwards, assume
# any existing key is already set correctly.
pass
else:
self.xref[generation][num] = offset
cnt += 1
num += 1
readNonWhitespace(stream)
stream.seek(-1, 1)
trailertag = stream.read(7)
if trailertag != "trailer":
# more xrefs!
stream.seek(-7, 1)
else:
break
readNonWhitespace(stream)
stream.seek(-1, 1)
newTrailer = readObject(stream, self)
for key, value in newTrailer.items():
if not self.trailer.has_key(key):
self.trailer[key] = value
if newTrailer.has_key("/Prev"):
startxref = newTrailer["/Prev"]
else:
break
elif x.isdigit():
# PDF 1.5+ Cross-Reference Stream
stream.seek(-1, 1)
idnum, generation = self.readObjectHeader(stream)
xrefstream = readObject(stream, self)
assert xrefstream["/Type"] == "/XRef"
self.cacheIndirectObject(generation, idnum, xrefstream)
streamData = StringIO(xrefstream.getData())
idx_pairs = xrefstream.get("/Index", [0, xrefstream.get("/Size")])
entrySizes = xrefstream.get("/W")
for num, size in self._pairs(idx_pairs):
cnt = 0
while cnt < size:
for i in range(len(entrySizes)):
d = streamData.read(entrySizes[i])
di = convertToInt(d, entrySizes[i])
if i == 0:
xref_type = di
elif i == 1:
if xref_type == 0:
next_free_object = di
elif xref_type == 1:
byte_offset = di
elif xref_type == 2:
objstr_num = di
elif i == 2:
if xref_type == 0:
next_generation = di
elif xref_type == 1:
generation = di
elif xref_type == 2:
obstr_idx = di
if xref_type == 0:
pass
elif xref_type == 1:
if not self.xref.has_key(generation):
self.xref[generation] = {}
if not num in self.xref[generation]:
self.xref[generation][num] = byte_offset
elif xref_type == 2:
if not num in self.xref_objStm:
self.xref_objStm[num] = [objstr_num, obstr_idx]
cnt += 1
num += 1
trailerKeys = "/Root", "/Encrypt", "/Info", "/ID"
for key in trailerKeys:
if xrefstream.has_key(key) and not self.trailer.has_key(key):
self.trailer[NameObject(key)] = xrefstream.raw_get(key)
if xrefstream.has_key("/Prev"):
startxref = xrefstream["/Prev"]
else:
break
else:
# bad xref character at startxref. Let's see if we can find
# the xref table nearby, as we've observed this error with an
# off-by-one before.
stream.seek(-11, 1)
tmp = stream.read(20)
print tmp
xref_loc = tmp.find("xref")
if xref_loc != -1:
startxref -= (10 - xref_loc)
continue
else:
# no xref table found at specified location
assert False
break
注: 私の例では、最後の 3 行でアサート False をスローしています。