123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485 |
- #!/usr/bin/env python
- import sys
- from pdfdevice import PDFTextDevice
- from pdffont import PDFUnicodeNotDefined
- from layout import LTContainer, LTPage, LTText, LTLine, LTRect, LTCurve
- from layout import LTFigure, LTImage, LTChar, LTTextLine
- from layout import LTTextBox, LTTextBoxVertical, LTTextGroup
- from utils import apply_matrix_pt, mult_matrix
- from utils import enc, bbox2str
- ## PDFLayoutAnalyzer
- ##
- class PDFLayoutAnalyzer(PDFTextDevice):
- def __init__(self, rsrcmgr, pageno=1, laparams=None):
- PDFTextDevice.__init__(self, rsrcmgr)
- self.pageno = pageno
- self.laparams = laparams
- self._stack = []
- return
- def begin_page(self, page, ctm):
- (x0, y0, x1, y1) = page.mediabox
- (x0, y0) = apply_matrix_pt(ctm, (x0, y0))
- (x1, y1) = apply_matrix_pt(ctm, (x1, y1))
- mediabox = (0, 0, abs(x0-x1), abs(y0-y1))
- self.cur_item = LTPage(self.pageno, mediabox)
- return
- def end_page(self, page):
- assert not self._stack
- assert isinstance(self.cur_item, LTPage)
- if self.laparams is not None:
- self.cur_item.analyze(self.laparams)
- self.pageno += 1
- self.receive_layout(self.cur_item)
- return
- def begin_figure(self, name, bbox, matrix):
- self._stack.append(self.cur_item)
- self.cur_item = LTFigure(name, bbox, mult_matrix(matrix, self.ctm))
- return
- def end_figure(self, _):
- fig = self.cur_item
- assert isinstance(self.cur_item, LTFigure)
- self.cur_item = self._stack.pop()
- self.cur_item.add(fig)
- return
- def render_image(self, name, stream):
- assert isinstance(self.cur_item, LTFigure)
- item = LTImage(name, stream,
- (self.cur_item.x0, self.cur_item.y0,
- self.cur_item.x1, self.cur_item.y1))
- self.cur_item.add(item)
- return
- def paint_path(self, gstate, stroke, fill, evenodd, path):
- shape = ''.join(x[0] for x in path)
- if shape == 'ml':
- # horizontal/vertical line
- (_, x0, y0) = path[0]
- (_, x1, y1) = path[1]
- (x0, y0) = apply_matrix_pt(self.ctm, (x0, y0))
- (x1, y1) = apply_matrix_pt(self.ctm, (x1, y1))
- if x0 == x1 or y0 == y1:
- self.cur_item.add(LTLine(gstate.linewidth, (x0, y0), (x1, y1)))
- return
- if shape == 'mlllh':
- # rectangle
- (_, x0, y0) = path[0]
- (_, x1, y1) = path[1]
- (_, x2, y2) = path[2]
- (_, x3, y3) = path[3]
- (x0, y0) = apply_matrix_pt(self.ctm, (x0, y0))
- (x1, y1) = apply_matrix_pt(self.ctm, (x1, y1))
- (x2, y2) = apply_matrix_pt(self.ctm, (x2, y2))
- (x3, y3) = apply_matrix_pt(self.ctm, (x3, y3))
- if ((x0 == x1 and y1 == y2 and x2 == x3 and y3 == y0) or
- (y0 == y1 and x1 == x2 and y2 == y3 and x3 == x0)):
- self.cur_item.add(LTRect(gstate.linewidth, (x0, y0, x2, y2)))
- return
- # other shapes
- pts = []
- for p in path:
- for i in xrange(1, len(p), 2):
- pts.append(apply_matrix_pt(self.ctm, (p[i], p[i+1])))
- self.cur_item.add(LTCurve(gstate.linewidth, pts))
- return
- def render_char(self, matrix, font, fontsize, scaling, rise, cid):
- try:
- text = font.to_unichr(cid)
- assert isinstance(text, unicode), text
- except PDFUnicodeNotDefined:
- text = self.handle_undefined_char(font, cid)
- textwidth = font.char_width(cid)
- textdisp = font.char_disp(cid)
- item = LTChar(matrix, font, fontsize, scaling, rise, text, textwidth, textdisp)
- self.cur_item.add(item)
- return item.adv
- def handle_undefined_char(self, font, cid):
- if self.debug:
- print >>sys.stderr, 'undefined: %r, %r' % (font, cid)
- return '(cid:%d)' % cid
- def receive_layout(self, ltpage):
- return
- ## PDFPageAggregator
- ##
- class PDFPageAggregator(PDFLayoutAnalyzer):
- def __init__(self, rsrcmgr, pageno=1, laparams=None):
- PDFLayoutAnalyzer.__init__(self, rsrcmgr, pageno=pageno, laparams=laparams)
- self.result = None
- return
- def receive_layout(self, ltpage):
- self.result = ltpage
- return
- def get_result(self):
- return self.result
- ## PDFConverter
- ##
- class PDFConverter(PDFLayoutAnalyzer):
- def __init__(self, rsrcmgr, outfp, codec='utf-8', pageno=1, laparams=None):
- PDFLayoutAnalyzer.__init__(self, rsrcmgr, pageno=pageno, laparams=laparams)
- self.outfp = outfp
- self.codec = codec
- return
- ## TextConverter
- ##
- class TextConverter(PDFConverter):
- def __init__(self, rsrcmgr, outfp, codec='utf-8', pageno=1, laparams=None,
- showpageno=False, imagewriter=None):
- PDFConverter.__init__(self, rsrcmgr, outfp, codec=codec, pageno=pageno, laparams=laparams)
- self.showpageno = showpageno
- self.imagewriter = imagewriter
- return
- def write_text(self, text):
- self.outfp.write(text.encode(self.codec, 'ignore'))
- return
- def receive_layout(self, ltpage):
- def render(item):
- if isinstance(item, LTContainer):
- for child in item:
- render(child)
- elif isinstance(item, LTText):
- self.write_text(item.get_text())
- if isinstance(item, LTTextBox):
- self.write_text('\n')
- elif isinstance(item, LTImage):
- if self.imagewriter is not None:
- self.imagewriter.export_image(item)
- if self.showpageno:
- self.write_text('Page %s\n' % ltpage.pageid)
- render(ltpage)
- self.write_text('\f')
- return
- # Some dummy functions to save memory/CPU when all that is wanted
- # is text. This stops all the image and drawing ouput from being
- # recorded and taking up RAM.
- def render_image(self, name, stream):
- if self.imagewriter is None:
- return
- PDFConverter.render_image(self, name, stream)
- return
- def paint_path(self, gstate, stroke, fill, evenodd, path):
- return
- ## HTMLConverter
- ##
- class HTMLConverter(PDFConverter):
- RECT_COLORS = {
- #'char': 'green',
- 'figure': 'yellow',
- 'textline': 'magenta',
- 'textbox': 'cyan',
- 'textgroup': 'red',
- 'curve': 'black',
- 'page': 'gray',
- }
- TEXT_COLORS = {
- 'textbox': 'blue',
- 'char': 'black',
- }
- def __init__(self, rsrcmgr, outfp, codec='utf-8', pageno=1, laparams=None,
- scale=1, fontscale=1.0, layoutmode='normal', showpageno=True,
- pagemargin=50, imagewriter=None,
- rect_colors={'curve': 'black', 'page': 'gray'},
- text_colors={'char': 'black'}):
- PDFConverter.__init__(self, rsrcmgr, outfp, codec=codec, pageno=pageno, laparams=laparams)
- self.scale = scale
- self.fontscale = fontscale
- self.layoutmode = layoutmode
- self.showpageno = showpageno
- self.pagemargin = pagemargin
- self.imagewriter = imagewriter
- self.rect_colors = rect_colors
- self.text_colors = text_colors
- if self.debug:
- self.rect_colors.update(self.RECT_COLORS)
- self.text_colors.update(self.TEXT_COLORS)
- self._yoffset = self.pagemargin
- self._font = None
- self._fontstack = []
- self.write_header()
- return
- def write(self, text):
- self.outfp.write(text)
- return
- def write_header(self):
- self.write('<html><head>\n')
- self.write('<meta http-equiv="Content-Type" content="text/html; charset=%s">\n' % self.codec)
- self.write('</head><body>\n')
- return
- def write_footer(self):
- self.write('<div style="position:absolute; top:0px;">Page: %s</div>\n' %
- ', '.join('<a href="#%s">%s</a>' % (i, i) for i in xrange(1, self.pageno)))
- self.write('</body></html>\n')
- return
- def write_text(self, text):
- self.write(enc(text, self.codec))
- return
- def place_rect(self, color, borderwidth, x, y, w, h):
- color = self.rect_colors.get(color)
- if color is not None:
- self.write('<span style="position:absolute; border: %s %dpx solid; '
- 'left:%dpx; top:%dpx; width:%dpx; height:%dpx;"></span>\n' %
- (color, borderwidth,
- x*self.scale, (self._yoffset-y)*self.scale,
- w*self.scale, h*self.scale))
- return
- def place_border(self, color, borderwidth, item):
- self.place_rect(color, borderwidth, item.x0, item.y1, item.width, item.height)
- return
- def place_image(self, item, borderwidth, x, y, w, h):
- if self.imagewriter is not None:
- name = self.imagewriter.export_image(item)
- self.write('<img src="%s" border="%d" style="position:absolute; left:%dpx; top:%dpx;" '
- 'width="%d" height="%d" />\n' %
- (enc(name), borderwidth,
- x*self.scale, (self._yoffset-y)*self.scale,
- w*self.scale, h*self.scale))
- return
- def place_text(self, color, text, x, y, size):
- color = self.text_colors.get(color)
- if color is not None:
- self.write('<span style="position:absolute; color:%s; left:%dpx; top:%dpx; font-size:%dpx;">' %
- (color, x*self.scale, (self._yoffset-y)*self.scale, size*self.scale*self.fontscale))
- self.write_text(text)
- self.write('</span>\n')
- return
- def begin_div(self, color, borderwidth, x, y, w, h, writing_mode=False):
- self._fontstack.append(self._font)
- self._font = None
- self.write('<div style="position:absolute; border: %s %dpx solid; writing-mode:%s; '
- 'left:%dpx; top:%dpx; width:%dpx; height:%dpx;">' %
- (color, borderwidth, writing_mode,
- x*self.scale, (self._yoffset-y)*self.scale,
- w*self.scale, h*self.scale))
- return
- def end_div(self, color):
- if self._font is not None:
- self.write('</span>')
- self._font = self._fontstack.pop()
- self.write('</div>')
- return
- def put_text(self, text, fontname, fontsize):
- font = (fontname, fontsize)
- if font != self._font:
- if self._font is not None:
- self.write('</span>')
- self.write('<span style="font-family: %s; font-size:%dpx">' %
- (fontname, fontsize * self.scale * self.fontscale))
- self._font = font
- self.write_text(text)
- return
- def put_newline(self):
- self.write('<br>')
- return
- def receive_layout(self, ltpage):
- def show_group(item):
- if isinstance(item, LTTextGroup):
- self.place_border('textgroup', 1, item)
- for child in item:
- show_group(child)
- return
- def render(item):
- if isinstance(item, LTPage):
- self._yoffset += item.y1
- self.place_border('page', 1, item)
- if self.showpageno:
- self.write('<div style="position:absolute; top:%dpx;">' %
- ((self._yoffset-item.y1)*self.scale))
- self.write('<a name="%s">Page %s</a></div>\n' % (item.pageid, item.pageid))
- for child in item:
- render(child)
- if item.groups is not None:
- for group in item.groups:
- show_group(group)
- elif isinstance(item, LTCurve):
- self.place_border('curve', 1, item)
- elif isinstance(item, LTFigure):
- self.begin_div('figure', 1, item.x0, item.y1, item.width, item.height)
- for child in item:
- render(child)
- self.end_div('figure')
- elif isinstance(item, LTImage):
- self.place_image(item, 1, item.x0, item.y1, item.width, item.height)
- else:
- if self.layoutmode == 'exact':
- if isinstance(item, LTTextLine):
- self.place_border('textline', 1, item)
- for child in item:
- render(child)
- elif isinstance(item, LTTextBox):
- self.place_border('textbox', 1, item)
- self.place_text('textbox', str(item.index+1), item.x0, item.y1, 20)
- for child in item:
- render(child)
- elif isinstance(item, LTChar):
- self.place_border('char', 1, item)
- self.place_text('char', item.get_text(), item.x0, item.y1, item.size)
- else:
- if isinstance(item, LTTextLine):
- for child in item:
- render(child)
- if self.layoutmode != 'loose':
- self.put_newline()
- elif isinstance(item, LTTextBox):
- self.begin_div('textbox', 1, item.x0, item.y1, item.width, item.height,
- item.get_writing_mode())
- for child in item:
- render(child)
- self.end_div('textbox')
- elif isinstance(item, LTChar):
- self.put_text(item.get_text(), item.fontname, item.size)
- elif isinstance(item, LTText):
- self.write_text(item.get_text())
- return
- render(ltpage)
- self._yoffset += self.pagemargin
- return
- def close(self):
- self.write_footer()
- return
- ## XMLConverter
- ##
- class XMLConverter(PDFConverter):
- def __init__(self, rsrcmgr, outfp, codec='utf-8', pageno=1,
- laparams=None, imagewriter=None):
- PDFConverter.__init__(self, rsrcmgr, outfp, codec=codec, pageno=pageno, laparams=laparams)
- self.imagewriter = imagewriter
- self.write_header()
- return
- def write_header(self):
- self.outfp.write('<?xml version="1.0" encoding="%s" ?>\n' % self.codec)
- self.outfp.write('<pages>\n')
- return
- def write_footer(self):
- self.outfp.write('</pages>\n')
- return
- def write_text(self, text):
- self.outfp.write(enc(text, self.codec))
- return
- def receive_layout(self, ltpage):
- def show_group(item):
- if isinstance(item, LTTextBox):
- self.outfp.write('<textbox id="%d" bbox="%s" />\n' %
- (item.index, bbox2str(item.bbox)))
- elif isinstance(item, LTTextGroup):
- self.outfp.write('<textgroup bbox="%s">\n' % bbox2str(item.bbox))
- for child in item:
- show_group(child)
- self.outfp.write('</textgroup>\n')
- return
- def render(item):
- if isinstance(item, LTPage):
- self.outfp.write('<page id="%s" bbox="%s" rotate="%d">\n' %
- (item.pageid, bbox2str(item.bbox), item.rotate))
- for child in item:
- render(child)
- if item.groups is not None:
- self.outfp.write('<layout>\n')
- for group in item.groups:
- show_group(group)
- self.outfp.write('</layout>\n')
- self.outfp.write('</page>\n')
- elif isinstance(item, LTLine):
- self.outfp.write('<line linewidth="%d" bbox="%s" />\n' %
- (item.linewidth, bbox2str(item.bbox)))
- elif isinstance(item, LTRect):
- self.outfp.write('<rect linewidth="%d" bbox="%s" />\n' %
- (item.linewidth, bbox2str(item.bbox)))
- elif isinstance(item, LTCurve):
- self.outfp.write('<curve linewidth="%d" bbox="%s" pts="%s"/>\n' %
- (item.linewidth, bbox2str(item.bbox), item.get_pts()))
- elif isinstance(item, LTFigure):
- self.outfp.write('<figure name="%s" bbox="%s">\n' %
- (item.name, bbox2str(item.bbox)))
- for child in item:
- render(child)
- self.outfp.write('</figure>\n')
- elif isinstance(item, LTTextLine):
- self.outfp.write('<textline bbox="%s">\n' % bbox2str(item.bbox))
- for child in item:
- render(child)
- self.outfp.write('</textline>\n')
- elif isinstance(item, LTTextBox):
- wmode = ''
- if isinstance(item, LTTextBoxVertical):
- wmode = ' wmode="vertical"'
- self.outfp.write('<textbox id="%d" bbox="%s"%s>\n' %
- (item.index, bbox2str(item.bbox), wmode))
- for child in item:
- render(child)
- self.outfp.write('</textbox>\n')
- elif isinstance(item, LTChar):
- self.outfp.write('<text font="%s" bbox="%s" size="%.3f">' %
- (enc(item.fontname), bbox2str(item.bbox), item.size))
- self.write_text(item.get_text())
- self.outfp.write('</text>\n')
- elif isinstance(item, LTText):
- self.outfp.write('<text>%s</text>\n' % item.get_text())
- elif isinstance(item, LTImage):
- if self.imagewriter is not None:
- name = self.imagewriter.export_image(item)
- self.outfp.write('<image src="%s" width="%d" height="%d" />\n' %
- (enc(name), item.width, item.height))
- else:
- self.outfp.write('<image width="%d" height="%d" />\n' %
- (item.width, item.height))
- else:
- assert 0, item
- return
- render(ltpage)
- return
- def close(self):
- self.write_footer()
- return
|