forked from josch/img2pdf
major refactoring
This commit is contained in:
parent
50aff1474f
commit
46a510d2b9
1 changed files with 158 additions and 138 deletions
226
img2pdf.py
226
img2pdf.py
|
@ -29,7 +29,7 @@ def parse(cont, indent=1):
|
|||
elif type(cont) is int or type(cont) is float:
|
||||
return str(cont)
|
||||
elif isinstance(cont, obj):
|
||||
return "%d 0 R"%cont.get_identifier()
|
||||
return "%d 0 R"%cont.identifier
|
||||
elif type(cont) is str:
|
||||
return cont
|
||||
elif type(cont) is list:
|
||||
|
@ -40,34 +40,20 @@ class obj():
|
|||
self.content = content
|
||||
self.stream = stream
|
||||
|
||||
def tostring(self, identifier):
|
||||
self.identifier = identifier
|
||||
def tostring(self):
|
||||
if self.stream:
|
||||
return "%d 0 obj "%identifier+parse(self.content)+"\nstream\n"+self.stream+"\nendstream\nendobj\n"
|
||||
return "%d 0 obj "%self.identifier+parse(self.content)+"\nstream\n"+self.stream+"\nendstream\nendobj\n"
|
||||
else:
|
||||
return "%d 0 obj "%identifier+parse(self.content)+" endobj\n"
|
||||
return "%d 0 obj "%self.identifier+parse(self.content)+" endobj\n"
|
||||
|
||||
def get_identifier(self):
|
||||
if not hasattr(self, 'identifier'):
|
||||
raise Exception("no id set yet, call tostring() on obj first")
|
||||
return self.identifier
|
||||
|
||||
def main(images, dpi, title=None, author=None, creator=None, producer=None,
|
||||
creationdate=None, moddate=None, subject=None, keywords=None,
|
||||
colorspace=None, verbose=False):
|
||||
|
||||
version = 3 # default pdf version 1.3
|
||||
class pdfdoc():
|
||||
objects = list()
|
||||
|
||||
def __init__(self, version=3, title=None, author=None, creator=None, producer=None,
|
||||
creationdate=None, moddate=None, subject=None, keywords=None):
|
||||
self.version = version # default pdf version 1.3
|
||||
now = datetime.now()
|
||||
|
||||
def debug_out(message):
|
||||
if verbose:
|
||||
sys.stderr.write("D: "+message+"\n")
|
||||
def error_out(message):
|
||||
sys.stderr.write("E: "+message+"\n")
|
||||
def warning_out(message):
|
||||
sys.stderr.write("W: "+message+"\n")
|
||||
|
||||
info = dict()
|
||||
if title:
|
||||
info["/Title"] = "("+title+")"
|
||||
|
@ -90,67 +76,37 @@ def main(images, dpi, title=None, author=None, creator=None, producer=None,
|
|||
if keywords:
|
||||
info["/Keywords"] = "("+",".join(keywords)+")"
|
||||
|
||||
info = obj(info)
|
||||
|
||||
pagestuples = list()
|
||||
self.info = obj(info)
|
||||
|
||||
# create an incomplete pages object so that a /Parent entry can be added to each page
|
||||
pages = obj({
|
||||
"/Type": "/Pages"
|
||||
self.pages = obj({
|
||||
"/Type": "/Pages",
|
||||
"/Kids": [],
|
||||
"/Count": 0
|
||||
})
|
||||
|
||||
for im in images:
|
||||
rawdata = im.read()
|
||||
im.seek(0)
|
||||
try:
|
||||
imgdata = Image.open(im)
|
||||
except IOError as e:
|
||||
# test if it is a jpeg2000 image
|
||||
if rawdata[:12] != "\x00\x00\x00\x0C\x6A\x50\x20\x20\x0D\x0A\x87\x0A":
|
||||
error_out("cannot read input image (not jpeg2000)")
|
||||
error_out("PIL: %s"%e)
|
||||
exit(1)
|
||||
# image is jpeg2000
|
||||
width, height, ics = parsejp2(rawdata)
|
||||
imgformat = "JP2"
|
||||
if colorspace:
|
||||
color = colorspace
|
||||
else:
|
||||
color = ics
|
||||
debug_out("input colorspace = %s"%(ics))
|
||||
if dpi:
|
||||
dpi_x, dpi_y = dpi, dpi
|
||||
else:
|
||||
dpi_x, dpi_y = (96, 96) # TODO: read real dpi
|
||||
else:
|
||||
width, height = imgdata.size
|
||||
if dpi:
|
||||
dpi_x, dpi_y = dpi, dpi
|
||||
else:
|
||||
dpi_x, dpi_y = imgdata.info.get("dpi", (96, 96))
|
||||
debug_out("input dpi = %d x %d"%(dpi_x,dpi_y))
|
||||
imgformat = imgdata.format
|
||||
if colorspace:
|
||||
color = colorspace
|
||||
else:
|
||||
color = imgdata.mode
|
||||
debug_out("input colorspace = %s"%(color))
|
||||
self.catalog = obj({
|
||||
"/Pages": self.pages,
|
||||
"/Type": "/Catalog"
|
||||
})
|
||||
self.addobj(self.catalog)
|
||||
self.addobj(self.pages)
|
||||
|
||||
debug_out("width x height = %d x %d"%(width,height))
|
||||
def addobj(self, obj):
|
||||
newid = len(self.objects)+1
|
||||
obj.identifier = newid
|
||||
self.objects.append(obj)
|
||||
|
||||
def addimage(self, color, width, height, dpi, imgformat, imgdata):
|
||||
if color == 'L':
|
||||
color = "/DeviceGray"
|
||||
elif color == 'RGB':
|
||||
color = "/DeviceRGB"
|
||||
elif color == '1':
|
||||
# TODO: /CCITTFaxDecode monochrome images
|
||||
imgdata = imgdata.convert('L')
|
||||
color = "/DeviceGray"
|
||||
else:
|
||||
error_out("unsupported color space: %s"%color)
|
||||
exit(1)
|
||||
|
||||
pdf_x, pdf_y = 72.0*width/dpi_x, 72.0*height/dpi_y # pdf units = 1/72 inch
|
||||
pdf_x, pdf_y = 72.0*width/dpi[0], 72.0*height/dpi[1] # pdf units = 1/72 inch
|
||||
|
||||
if pdf_x < 3.00 or pdf_y < 3.00:
|
||||
warning_out("pdf width or height is below 3.00 - decrease the dpi")
|
||||
|
@ -158,16 +114,11 @@ def main(images, dpi, title=None, author=None, creator=None, producer=None,
|
|||
# either embed the whole jpeg or deflate the bitmap representation
|
||||
if imgformat is "JPEG":
|
||||
ofilter = [ "/DCTDecode" ]
|
||||
imgdata = rawdata
|
||||
elif imgformat is "JP2":
|
||||
ofilter = [ "/JPXDecode" ]
|
||||
imgdata = rawdata
|
||||
version = 5 # jpeg2000 needs pdf 1.5
|
||||
self.version = 5 # jpeg2000 needs pdf 1.5
|
||||
else:
|
||||
ofilter = [ "/FlateDecode" ]
|
||||
imgdata = zlib.compress(imgdata.tostring())
|
||||
im.close()
|
||||
|
||||
image = obj({
|
||||
"/Type": "/XObject",
|
||||
"/Subtype": "/Image",
|
||||
|
@ -187,7 +138,7 @@ def main(images, dpi, title=None, author=None, creator=None, producer=None,
|
|||
|
||||
page = obj({
|
||||
"/Type": "/Page",
|
||||
"/Parent": pages,
|
||||
"/Parent": self.pages,
|
||||
"/Resources": {
|
||||
"/XObject": {
|
||||
"/Im0": image
|
||||
|
@ -196,37 +147,24 @@ def main(images, dpi, title=None, author=None, creator=None, producer=None,
|
|||
"/MediaBox": [0, 0, pdf_x, pdf_y],
|
||||
"/Contents": content
|
||||
})
|
||||
self.pages.content["/Kids"].append(page)
|
||||
self.pages.content["/Count"] += 1
|
||||
self.addobj(page)
|
||||
self.addobj(content)
|
||||
self.addobj(image)
|
||||
|
||||
pagestuples.append((image, content, page))
|
||||
|
||||
# complete pages object with page information
|
||||
pages.content["/Kids"] = [ pagetuple[2] for pagetuple in pagestuples ]
|
||||
pages.content["/Count"] = len(pagestuples)
|
||||
|
||||
catalog = obj({
|
||||
"/Pages": pages,
|
||||
"/Type": "/Catalog"
|
||||
})
|
||||
|
||||
objects = list()
|
||||
objects.append(info.tostring(3*(len(pagestuples)+1)))
|
||||
pages.identifier = 2 # manually set it because each page references to it
|
||||
for i, (image, content, page) in enumerate(reversed(pagestuples)):
|
||||
objects.append(image.tostring(3*(len(pagestuples)-i+1)-1))
|
||||
objects.append(content.tostring(3*(len(pagestuples)-i+1)-2))
|
||||
objects.append(page.tostring(3*(len(pagestuples)-i+1)-3))
|
||||
objects.append(pages.tostring(2))
|
||||
objects.append(catalog.tostring(1))
|
||||
objects.reverse()
|
||||
def tostring(self):
|
||||
# add info as last object
|
||||
self.addobj(self.info)
|
||||
|
||||
xreftable = list()
|
||||
|
||||
result = "%%PDF-1.%d\n"%version
|
||||
result = "%%PDF-1.%d\n"%self.version
|
||||
|
||||
xreftable.append("0000000000 65535 f \n")
|
||||
for o in objects:
|
||||
for o in self.objects:
|
||||
xreftable.append("%010d 00000 n \n"%len(result))
|
||||
result += o
|
||||
result += o.tostring()
|
||||
|
||||
xrefoffset = len(result)
|
||||
result += "xref\n"
|
||||
|
@ -234,13 +172,95 @@ def main(images, dpi, title=None, author=None, creator=None, producer=None,
|
|||
for x in xreftable:
|
||||
result += x
|
||||
result += "trailer\n"
|
||||
result += parse({"/Size": len(xreftable), "/Info": info, "/Root": catalog})+"\n"
|
||||
result += parse({"/Size": len(xreftable), "/Info": self.info, "/Root": self.catalog})+"\n"
|
||||
result += "startxref\n"
|
||||
result += "%d\n"%xrefoffset
|
||||
result += "%%EOF\n"
|
||||
|
||||
return result
|
||||
|
||||
def main(images, dpi, title=None, author=None, creator=None, producer=None,
|
||||
creationdate=None, moddate=None, subject=None, keywords=None,
|
||||
colorspace=None, verbose=False):
|
||||
|
||||
def debug_out(message):
|
||||
if verbose:
|
||||
sys.stderr.write("D: "+message+"\n")
|
||||
def error_out(message):
|
||||
sys.stderr.write("E: "+message+"\n")
|
||||
def warning_out(message):
|
||||
sys.stderr.write("W: "+message+"\n")
|
||||
|
||||
pdf = pdfdoc()
|
||||
|
||||
for im in images:
|
||||
rawdata = im.read()
|
||||
im.seek(0)
|
||||
try:
|
||||
imgdata = Image.open(im)
|
||||
except IOError as e:
|
||||
# test if it is a jpeg2000 image
|
||||
if rawdata[:12] != "\x00\x00\x00\x0C\x6A\x50\x20\x20\x0D\x0A\x87\x0A":
|
||||
error_out("cannot read input image (not jpeg2000)")
|
||||
error_out("PIL: %s"%e)
|
||||
exit(1)
|
||||
# image is jpeg2000
|
||||
width, height, ics = parsejp2(rawdata)
|
||||
imgformat = "JP2"
|
||||
|
||||
if dpi:
|
||||
dpi = dpi, dpi
|
||||
debug_out("input dpi (forced) = %d x %d"%dpi)
|
||||
else:
|
||||
dpi = (96, 96) # TODO: read real dpi
|
||||
debug_out("input dpi = %d x %d"%dpi)
|
||||
|
||||
if colorspace:
|
||||
color = colorspace
|
||||
debug_out("input colorspace (forced) = %s"%(ics))
|
||||
else:
|
||||
color = ics
|
||||
debug_out("input colorspace = %s"%(ics))
|
||||
else:
|
||||
width, height = imgdata.size
|
||||
imgformat = imgdata.format
|
||||
|
||||
if dpi:
|
||||
dpi = dpi, dpi
|
||||
debug_out("input dpi (forced) = %d x %d"%dpi)
|
||||
else:
|
||||
dpi = imgdata.info.get("dpi", (96, 96))
|
||||
debug_out("input dpi = %d x %d"%dpi)
|
||||
|
||||
if colorspace:
|
||||
color = colorspace
|
||||
debug_out("input colorspace (forced) = %s"%(color))
|
||||
else:
|
||||
color = imgdata.mode
|
||||
debug_out("input colorspace = %s"%(color))
|
||||
|
||||
debug_out("width x height = %d x %d"%(width,height))
|
||||
debug_out("imgformat = %s"%imgformat)
|
||||
|
||||
# depending on the input format, determine whether to pass the raw
|
||||
# image or the zlib compressed color information
|
||||
if imgformat is "JPEG" or imgformat is "JP2":
|
||||
if color == '1':
|
||||
error_out("jpeg can't be monochrome")
|
||||
exit(1)
|
||||
imgdata = rawdata
|
||||
else:
|
||||
# because we do not support /CCITTFaxDecode
|
||||
if color == '1':
|
||||
imgdata = imgdata.convert('L')
|
||||
color = 'L'
|
||||
imgdata = zlib.compress(imgdata.tostring())
|
||||
|
||||
pdf.addimage(color, width, height, dpi, imgformat, imgdata)
|
||||
|
||||
im.close()
|
||||
|
||||
return pdf.tostring()
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description='lossless conversion/embedding of images (in)to pdf')
|
||||
parser.add_argument('images', metavar='infile', type=argparse.FileType('rb'),
|
||||
|
|
Loading…
Reference in a new issue