home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2010 November / maximum-cd-2010-11.iso / DiscContents / calibre-0.7.13.msi / file_1059 (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2010-08-06  |  26.4 KB  |  803 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.6)
  3.  
  4. from __future__ import with_statement
  5. __license__ = 'GPL v3'
  6. __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
  7. __docformat__ = 'restructuredtext en'
  8. import sys
  9. import os
  10. from lxml import etree
  11.  
  12. class Font(object):
  13.     
  14.     def __init__(self, spec):
  15.         self.id = spec.get('id')
  16.         self.size = float(spec.get('size'))
  17.         self.color = spec.get('color')
  18.         self.family = spec.get('family')
  19.  
  20.  
  21.  
  22. class Element(object):
  23.     
  24.     def __init__(self):
  25.         self.starts_block = None
  26.         self.block_style = None
  27.  
  28.     
  29.     def __eq__(self, other):
  30.         return self.id == other.id
  31.  
  32.     
  33.     def __hash__(self):
  34.         return hash(self.id)
  35.  
  36.  
  37.  
  38. class Image(Element):
  39.     
  40.     def __init__(self, img, opts, log, idc):
  41.         Element.__init__(self)
  42.         self.opts = opts
  43.         self.log = log
  44.         self.id = idc.next()
  45.         (self.top, self.left, self.width, self.height, self.iwidth, self.iheight) = map(float, map(img.get, ('top', 'left', 'rwidth', 'rheight', 'iwidth', 'iheight')))
  46.         self.src = img.get('src')
  47.         self.bottom = self.top + self.height
  48.         self.right = self.left + self.width
  49.  
  50.     
  51.     def to_html(self):
  52.         return '<img src="%s" width="%dpx" height="%dpx"/>' % (self.src, int(self.width), int(self.height))
  53.  
  54.     
  55.     def dump(self, f):
  56.         f.write(self.to_html())
  57.         f.write('\n')
  58.  
  59.  
  60.  
  61. class Text(Element):
  62.     
  63.     def __init__(self, text, font_map, opts, log, idc):
  64.         Element.__init__(self)
  65.         self.id = idc.next()
  66.         self.opts = opts
  67.         self.log = log
  68.         self.font_map = font_map
  69.         (self.top, self.left, self.width, self.height) = map(float, map(text.get, ('top', 'left', 'width', 'height')))
  70.         self.bottom = self.top + self.height
  71.         self.right = self.left + self.width
  72.         self.font = self.font_map[text.get('font')]
  73.         self.font_size = self.font.size
  74.         self.color = self.font.color
  75.         self.font_family = self.font.family
  76.         text.tail = ''
  77.         self.text_as_string = etree.tostring(text, method = 'text', encoding = unicode)
  78.         self.raw = None if text.text else u''
  79.         for x in text.iterchildren():
  80.             self.raw += etree.tostring(x, method = 'xml', encoding = unicode)
  81.         
  82.         self.average_character_width = self.width / len(self.text_as_string)
  83.  
  84.     
  85.     def coalesce(self, other, page_number):
  86.         if self.opts.verbose > 2:
  87.             self.log.debug('Coalescing %r with %r on page %d' % (self.text_as_string, other.text_as_string, page_number))
  88.         
  89.         self.top = min(self.top, other.top)
  90.         self.right = other.right
  91.         self.width = self.right - self.left
  92.         self.bottom = max(self.bottom, other.bottom)
  93.         self.height = self.bottom - self.top
  94.         self.font_size = max(self.font_size, other.font_size)
  95.         self.font = None if self.font_size == other.font_size else other.font
  96.         self.text_as_string += other.text_as_string
  97.         self.raw += other.raw
  98.         self.average_character_width = (self.average_character_width + other.average_character_width) / 2
  99.  
  100.     
  101.     def to_html(self):
  102.         return self.raw
  103.  
  104.     
  105.     def dump(self, f):
  106.         f.write(self.to_html().encode('utf-8'))
  107.         f.write('\n')
  108.  
  109.  
  110.  
  111. class FontSizeStats(dict):
  112.     
  113.     def __init__(self, stats):
  114.         total = float(sum(stats.values()))
  115.         (self.most_common_size, self.chars_at_most_common_size) = (-1, 0)
  116.         for sz, chars in stats.items():
  117.             if chars >= self.chars_at_most_common_size:
  118.                 self.most_common_size = sz
  119.                 self.chars_at_most_common_size = chars
  120.             
  121.             self[sz] = chars / total
  122.         
  123.  
  124.  
  125.  
  126. class Interval(object):
  127.     
  128.     def __init__(self, left, right):
  129.         self.left = left
  130.         self.right = right
  131.         self.width = right - left
  132.  
  133.     
  134.     def intersection(self, other):
  135.         left = max(self.left, other.left)
  136.         right = min(self.right, other.right)
  137.         return Interval(left, right)
  138.  
  139.     
  140.     def centered_in(self, parent):
  141.         left = abs(self.left - parent.left)
  142.         right = abs(self.right - parent.right)
  143.         return abs(left - right) < 3
  144.  
  145.     
  146.     def __nonzero__(self):
  147.         return self.width > 0
  148.  
  149.     
  150.     def __eq__(self, other):
  151.         if self.left == other.left:
  152.             pass
  153.         return self.right == other.right
  154.  
  155.     
  156.     def __hash__(self):
  157.         return hash('(%f,%f)' % self.left, self.right)
  158.  
  159.  
  160.  
  161. class Column(object):
  162.     HFUZZ = 0.2
  163.     
  164.     def __init__(self):
  165.         self.left = self.right = self.top = self.bottom = 0
  166.         self.width = self.height = 0
  167.         self.elements = []
  168.         self.average_line_separation = 0
  169.  
  170.     
  171.     def add(self, elem):
  172.         if elem in self.elements:
  173.             return None
  174.         self.elements.append(elem)
  175.         self._post_add()
  176.  
  177.     
  178.     def prepend(self, elem):
  179.         if elem in self.elements:
  180.             return None
  181.         self.elements.insert(0, elem)
  182.         self._post_add()
  183.  
  184.     
  185.     def _post_add(self):
  186.         self.elements.sort(cmp = (lambda x, y: cmp(x.bottom, y.bottom)))
  187.         self.top = self.elements[0].top
  188.         self.bottom = self.elements[-1].bottom
  189.         self.left = sys.maxint
  190.         self.right = 0
  191.         for x in self:
  192.             self.left = min(self.left, x.left)
  193.             self.right = max(self.right, x.right)
  194.         
  195.         self.width = self.right - self.left
  196.         self.height = self.bottom - self.top
  197.  
  198.     
  199.     def __iter__(self):
  200.         for x in self.elements:
  201.             yield x
  202.         
  203.  
  204.     
  205.     def __len__(self):
  206.         return len(self.elements)
  207.  
  208.     
  209.     def contains(self, elem):
  210.         if elem.left > self.left - self.HFUZZ * self.width:
  211.             pass
  212.         return elem.right < self.right + self.HFUZZ * self.width
  213.  
  214.     
  215.     def collect_stats(self):
  216.         for i, elem in enumerate(self.elements):
  217.             left_margin = elem.left - self.left
  218.             elem.indent_fraction = left_margin / self.width
  219.             elem.width_fraction = elem.width / self.width
  220.             if i == 0:
  221.                 elem.top_gap_ratio = None
  222.                 continue
  223.             None if len(self.elements) > 1 else []
  224.             elem.top_gap_ratio = (self.elements[i - 1].bottom - elem.top) / self.average_line_separation
  225.         
  226.  
  227.     
  228.     def previous_element(self, idx):
  229.         if idx == 0:
  230.             return None
  231.         return self.elements[idx - 1]
  232.  
  233.     
  234.     def dump(self, f, num):
  235.         f.write('******** Column %d\n\n' % num)
  236.         for elem in self.elements:
  237.             elem.dump(f)
  238.         
  239.  
  240.  
  241.  
  242. class Box(list):
  243.     
  244.     def __init__(self, type = 'p'):
  245.         self.tag = type
  246.  
  247.     
  248.     def to_html(self):
  249.         ans = [
  250.             '<%s>' % self.tag]
  251.         for elem in self:
  252.             if isinstance(elem, int):
  253.                 ans.append('<a name="page_%d"/>' % elem)
  254.                 continue
  255.             ans.append(elem.to_html() + ' ')
  256.         
  257.         ans.append('</%s>' % self.tag)
  258.         return ans
  259.  
  260.  
  261.  
  262. class ImageBox(Box):
  263.     
  264.     def __init__(self, img):
  265.         Box.__init__(self)
  266.         self.img = img
  267.  
  268.     
  269.     def to_html(self):
  270.         ans = [
  271.             '<div style="text-align:center">']
  272.         ans.append(self.img.to_html())
  273.         if len(self) > 0:
  274.             ans.append('<br/>')
  275.             for elem in self:
  276.                 if isinstance(elem, int):
  277.                     ans.append('<a name="page_%d"/>' % elem)
  278.                     continue
  279.                 ans.append(elem.to_html() + ' ')
  280.             
  281.         
  282.         ans.append('</div>')
  283.         return ans
  284.  
  285.  
  286.  
  287. class Region(object):
  288.     
  289.     def __init__(self, opts, log):
  290.         self.opts = opts
  291.         self.log = log
  292.         self.columns = []
  293.         self.top = self.bottom = self.left = self.right = self.width = self.height = 0
  294.  
  295.     
  296.     def add(self, columns):
  297.         if not self.columns:
  298.             for x in sorted(columns, cmp = (lambda x, y: cmp(x.left, y.left))):
  299.                 self.columns.append(x)
  300.             
  301.         else:
  302.             for i in range(len(columns)):
  303.                 for elem in columns[i]:
  304.                     self.columns[i].add(elem)
  305.                 
  306.             
  307.  
  308.     
  309.     def contains(self, columns):
  310.         if not self.columns:
  311.             return True
  312.         if len(columns) != len(self.columns):
  313.             return False
  314.         for i in range(len(columns)):
  315.             c1 = self.columns[i]
  316.             c2 = columns[i]
  317.             x1 = Interval(c1.left, c1.right)
  318.             x2 = Interval(c2.left, c2.right)
  319.             intersection = x1.intersection(x2)
  320.             base = min(x1.width, x2.width)
  321.             if intersection.width / base < 0.6:
  322.                 return False
  323.         
  324.         return True
  325.  
  326.     
  327.     def is_empty(self):
  328.         return len(self.columns) == 0
  329.  
  330.     is_empty = property(is_empty)
  331.     
  332.     def line_count(self):
  333.         max_lines = 0
  334.         for c in self.columns:
  335.             max_lines = max(max_lines, len(c))
  336.         
  337.         return max_lines
  338.  
  339.     line_count = property(line_count)
  340.     
  341.     def is_small(self):
  342.         return self.line_count < 3
  343.  
  344.     is_small = property(is_small)
  345.     
  346.     def absorb(self, singleton):
  347.         
  348.         def most_suitable_column(elem):
  349.             (mc, mw) = (None, 0)
  350.             for c in self.columns:
  351.                 i = Interval(c.left, c.right)
  352.                 e = Interval(elem.left, elem.right)
  353.                 w = i.intersection(e).width
  354.                 if w > mw:
  355.                     mc = c
  356.                     mw = w
  357.                     continue
  358.             
  359.             if mc is None:
  360.                 self.log.warn('No suitable column for singleton', elem.to_html())
  361.                 mc = self.columns[0]
  362.             
  363.             return mc
  364.  
  365.         for c in singleton.columns:
  366.             for elem in c:
  367.                 col = most_suitable_column(elem)
  368.                 if self.opts.verbose > 3:
  369.                     idx = self.columns.index(col)
  370.                     self.log.debug(u'Absorbing singleton %s into column' % elem.to_html(), idx)
  371.                 
  372.                 col.add(elem)
  373.             
  374.         
  375.  
  376.     
  377.     def collect_stats(self):
  378.         for column in self.columns:
  379.             column.collect_stats()
  380.         
  381.         self.average_line_separation = []([ x.average_line_separation for x in self.columns ]) / float(len(self.columns))
  382.  
  383.     
  384.     def __iter__(self):
  385.         for x in self.columns:
  386.             yield x
  387.         
  388.  
  389.     
  390.     def absorb_regions(self, regions, at):
  391.         for region in regions:
  392.             self.absorb_region(region, at)
  393.         
  394.  
  395.     
  396.     def absorb_region(self, region, at):
  397.         if len(region.columns) <= len(self.columns):
  398.             for i in range(len(region.columns)):
  399.                 src = region.columns[i]
  400.                 dest = self.columns[i]
  401.                 if at != 'bottom':
  402.                     src = reversed(list(iter(src)))
  403.                 
  404.                 for elem in src:
  405.                     func = None if at == 'bottom' else dest.prepend
  406.                     func(elem)
  407.                 
  408.             
  409.         else:
  410.             col_map = { }
  411.             for i, col in enumerate(region.columns):
  412.                 (max_overlap, max_overlap_index) = (0, 0)
  413.                 for j, dcol in enumerate(self.columns):
  414.                     sint = Interval(col.left, col.right)
  415.                     dint = Interval(dcol.left, dcol.right)
  416.                     width = sint.intersection(dint).width
  417.                     if width > max_overlap:
  418.                         max_overlap = width
  419.                         max_overlap_index = j
  420.                         continue
  421.                 
  422.                 col_map[i] = max_overlap_index
  423.             
  424.             lines = max(map(len, region.columns))
  425.             if at == 'bottom':
  426.                 lines = range(lines)
  427.             else:
  428.                 lines = range(lines - 1, -1, -1)
  429.             for i in lines:
  430.                 for j, src in enumerate(region.columns):
  431.                     dest = self.columns[col_map[j]]
  432.                     if i < len(src):
  433.                         func = None if at == 'bottom' else dest.prepend
  434.                         func(src.elements[i])
  435.                         continue
  436.                 
  437.             
  438.  
  439.     
  440.     def dump(self, f):
  441.         f.write('############################################################\n')
  442.         f.write('########## Region (%d columns) ###############\n' % len(self.columns))
  443.         f.write('############################################################\n\n')
  444.         for i, col in enumerate(self.columns):
  445.             col.dump(f, i)
  446.         
  447.  
  448.     
  449.     def linearize(self):
  450.         self.elements = []
  451.         for x in self.columns:
  452.             self.elements.extend(x)
  453.         
  454.         self.boxes = [
  455.             Box()]
  456.         for i, elem in enumerate(self.elements):
  457.             if isinstance(elem, Image):
  458.                 self.boxes.append(ImageBox(elem))
  459.                 img = Interval(elem.left, elem.right)
  460.                 for j in range(i + 1, len(self.elements)):
  461.                     t = self.elements[j]
  462.                     if not isinstance(t, Text):
  463.                         break
  464.                     
  465.                     ti = Interval(t.left, t.right)
  466.                     if not ti.centered_in(img):
  467.                         break
  468.                     
  469.                     self.boxes[-1].append(t)
  470.                 
  471.                 self.boxes.append(Box())
  472.                 continue
  473.             is_indented = False
  474.             if i + 1 < len(self.elements):
  475.                 indent_diff = elem.indent_fraction - self.elements[i + 1].indent_fraction
  476.                 if indent_diff > 0.05:
  477.                     is_indented = True
  478.                 
  479.             
  480.             if elem.top_gap_ratio > 1.2 or is_indented:
  481.                 self.boxes.append(Box())
  482.             
  483.             self.boxes[-1].append(elem)
  484.         
  485.  
  486.  
  487.  
  488. class Page(object):
  489.     COALESCE_FACTOR = 0.5
  490.     LINE_FACTOR = 0.4
  491.     YFUZZ = 1.5
  492.     
  493.     def __init__(self, page, font_map, opts, log, idc):
  494.         self.opts = opts
  495.         self.log = log
  496.         self.font_map = font_map
  497.         self.number = int(page.get('number'))
  498.         (self.width, self.height) = map(float, map(page.get, ('width', 'height')))
  499.         self.id = 'page%d' % self.number
  500.         self.texts = []
  501.         self.left_margin = self.width
  502.         self.right_margin = 0
  503.         for text in page.xpath('descendant::text'):
  504.             self.texts.append(Text(text, self.font_map, self.opts, self.log, idc))
  505.             text = self.texts[-1]
  506.             self.left_margin = min(text.left, self.left_margin)
  507.             self.right_margin = max(text.right, self.right_margin)
  508.         
  509.         self.textwidth = self.right_margin - self.left_margin
  510.         self.font_size_stats = { }
  511.         self.average_text_height = 0
  512.         for t in self.texts:
  513.             if t.font_size not in self.font_size_stats:
  514.                 self.font_size_stats[t.font_size] = 0
  515.             
  516.             self.font_size_stats[t.font_size] += len(t.text_as_string)
  517.             self.average_text_height += t.height
  518.         
  519.         self.font_size_stats = FontSizeStats(self.font_size_stats)
  520.         self.coalesce_fragments()
  521.         self.elements = list(self.texts)
  522.         for img in page.xpath('descendant::img'):
  523.             self.elements.append(Image(img, self.opts, self.log, idc))
  524.         
  525.         self.elements.sort(cmp = (lambda x, y: cmp(x.top, y.top)))
  526.  
  527.     
  528.     def coalesce_fragments(self):
  529.         
  530.         def find_match(frag):
  531.             for t in self.texts:
  532.                 hdelta = t.left - frag.right
  533.                 hoverlap = self.COALESCE_FACTOR * frag.average_character_width
  534.                 if t is not frag and hdelta > -hoverlap and hdelta < hoverlap and abs(t.bottom - frag.bottom) < self.LINE_FACTOR * frag.height:
  535.                     return t
  536.             
  537.  
  538.         match_found = True
  539.         while match_found:
  540.             match_found = False
  541.             match = None
  542.             for frag in self.texts:
  543.                 match = find_match(frag)
  544.                 if match is not None:
  545.                     match_found = True
  546.                     frag.coalesce(match, self.number)
  547.                     break
  548.                     continue
  549.                 (None,)
  550.             
  551.             if match is not None:
  552.                 self.texts.remove(match)
  553.                 continue
  554.  
  555.     
  556.     def first_pass(self):
  557.         self.regions = []
  558.         if not self.elements:
  559.             return None
  560.         for i, x in enumerate(self.elements):
  561.             x.idx = i
  562.         
  563.         current_region = Region(self.opts, self.log)
  564.         processed = set([])
  565.         for x in self.elements:
  566.             if x in processed:
  567.                 continue
  568.             
  569.             elems = set(self.find_elements_in_row_of(x))
  570.             columns = self.sort_into_columns(x, elems)
  571.             processed.update(elems)
  572.             if not current_region.contains(columns):
  573.                 self.regions.append(current_region)
  574.                 current_region = Region(self.opts, self.log)
  575.             
  576.             current_region.add(columns)
  577.         
  578.         if not current_region.is_empty:
  579.             self.regions.append(current_region)
  580.         
  581.         if self.opts.verbose > 2:
  582.             self.debug_dir = 'page-%d' % self.number
  583.             os.mkdir(self.debug_dir)
  584.             self.dump_regions('pre-coalesce')
  585.         
  586.         self.coalesce_regions()
  587.         self.dump_regions('post-coalesce')
  588.  
  589.     
  590.     def dump_regions(self, fname):
  591.         fname = 'regions-' + fname + '.txt'
  592.         
  593.         try:
  594.             f = _[1]
  595.             f.write('Page #%d\n\n' % self.number)
  596.             for region in self.regions:
  597.                 region.dump(f)
  598.         finally:
  599.             pass
  600.  
  601.  
  602.     
  603.     def coalesce_regions(self):
  604.         found = True
  605.         absorbed = set([])
  606.         processed = set([])
  607.         while found:
  608.             found = False
  609.             for i, region in enumerate(self.regions):
  610.                 if region in absorbed:
  611.                     continue
  612.                 
  613.                 if region.is_small and region not in processed:
  614.                     found = True
  615.                     processed.add(region)
  616.                     regions = [
  617.                         region]
  618.                     end = i + 1
  619.                     for j in range(i + 1, len(self.regions)):
  620.                         end = j
  621.                         if self.regions[j].is_small:
  622.                             regions.append(self.regions[j])
  623.                             continue
  624.                     
  625.                     prev_region = None if i == 0 else i - 1
  626.                     next_region = None if end < len(self.regions) and self.regions[end] not in regions else None
  627.                     absorb_at = 'bottom'
  628.                     if prev_region is None and next_region is not None:
  629.                         absorb_into = next_region
  630.                         absorb_at = 'top'
  631.                     elif next_region is None and prev_region is not None:
  632.                         absorb_into = prev_region
  633.                     elif prev_region is None and next_region is None:
  634.                         if len(regions) > 1:
  635.                             absorb_into = i
  636.                             regions = regions[1:]
  637.                         else:
  638.                             absorb_into = None
  639.                     else:
  640.                         absorb_into = prev_region
  641.                     if absorb_into is not None:
  642.                         self.regions[absorb_into].absorb_regions(regions, absorb_at)
  643.                         absorbed.update(regions)
  644.                     
  645.                 absorb_into is not None
  646.             
  647.             continue
  648.             None if self.regions[next_region].line_count >= self.regions[prev_region].line_count else sum
  649.         for region in absorbed:
  650.             self.regions.remove(region)
  651.         
  652.  
  653.     
  654.     def sort_into_columns(self, elem, neighbors):
  655.         neighbors.add(elem)
  656.         neighbors = sorted(neighbors, cmp = (lambda x, y: cmp(x.left, y.left)))
  657.         columns = [
  658.             Column()]
  659.         columns[0].add(elem)
  660.         for x in neighbors:
  661.             added = False
  662.             for c in columns:
  663.                 if c.contains(x):
  664.                     c.add(x)
  665.                     added = True
  666.                     break
  667.                     continue
  668.                 None if self.opts.verbose > 3 else self.log.debug
  669.             
  670.             if not added:
  671.                 columns.append(Column())
  672.                 columns[-1].add(x)
  673.                 columns.sort(cmp = (lambda x, y: cmp(x.left, y.left)))
  674.                 continue
  675.         
  676.         return columns
  677.  
  678.     
  679.     def find_elements_in_row_of(self, x):
  680.         interval = Interval(x.top, x.top + self.YFUZZ * self.average_text_height)
  681.         h_interval = Interval(x.left, x.right)
  682.         for y in self.elements[x.idx:x.idx + 15]:
  683.             if y is not x:
  684.                 y_interval = Interval(y.top, y.bottom)
  685.                 x_interval = Interval(y.left, y.right)
  686.                 if interval.intersection(y_interval).width > 0.5 * self.average_text_height and x_interval.intersection(h_interval).width <= 0:
  687.                     yield y
  688.                 
  689.             x_interval.intersection(h_interval).width <= 0
  690.         
  691.  
  692.     
  693.     def second_pass(self):
  694.         for region in self.regions:
  695.             region.collect_stats()
  696.             region.linearize()
  697.         
  698.  
  699.  
  700.  
  701. class PDFDocument(object):
  702.     
  703.     def __init__(self, xml, opts, log):
  704.         self.opts = opts
  705.         self.log = log
  706.         parser = etree.XMLParser(recover = True)
  707.         self.root = etree.fromstring(xml, parser = parser)
  708.         idc = iter(xrange(sys.maxint))
  709.         self.fonts = []
  710.         self.font_map = { }
  711.         for spec in self.root.xpath('//font'):
  712.             self.fonts.append(Font(spec))
  713.             self.font_map[self.fonts[-1].id] = self.fonts[-1]
  714.         
  715.         self.pages = []
  716.         self.page_map = { }
  717.         for page in self.root.xpath('//page'):
  718.             page = Page(page, self.font_map, opts, log, idc)
  719.             self.page_map[page.id] = page
  720.             self.pages.append(page)
  721.         
  722.         self.collect_font_statistics()
  723.         for page in self.pages:
  724.             page.document_font_stats = self.font_size_stats
  725.             page.first_pass()
  726.             page.second_pass()
  727.         
  728.         self.linearize()
  729.         self.render()
  730.  
  731.     
  732.     def collect_font_statistics(self):
  733.         self.font_size_stats = { }
  734.         for p in self.pages:
  735.             for sz in p.font_size_stats:
  736.                 chars = p.font_size_stats[sz]
  737.                 if sz not in self.font_size_stats:
  738.                     self.font_size_stats[sz] = 0
  739.                 
  740.                 self.font_size_stats[sz] += chars
  741.             
  742.         
  743.         self.font_size_stats = FontSizeStats(self.font_size_stats)
  744.  
  745.     
  746.     def linearize(self):
  747.         self.elements = []
  748.         last_region = None
  749.         last_block = None
  750.         for page in self.pages:
  751.             page_number_inserted = False
  752.             for region in page.regions:
  753.                 if last_region is not None and len(last_region.columns) == len(region.columns):
  754.                     pass
  755.                 merge_first_block = not hasattr(last_block, 'img')
  756.                 for i, block in enumerate(region.boxes):
  757.                     if merge_first_block:
  758.                         merge_first_block = False
  759.                         if not page_number_inserted:
  760.                             last_block.append(page.number)
  761.                             page_number_inserted = True
  762.                         
  763.                         for elem in block:
  764.                             last_block.append(elem)
  765.                         
  766.                     elif not page_number_inserted:
  767.                         block.insert(0, page.number)
  768.                         page_number_inserted = True
  769.                     
  770.                     self.elements.append(block)
  771.                     last_block = block
  772.                 
  773.                 last_region = region
  774.             
  775.         
  776.  
  777.     
  778.     def render(self):
  779.         html = [
  780.             '<?xml version="1.0" encoding="UTF-8"?>',
  781.             '<html xmlns="http://www.w3.org/1999/xhtml">',
  782.             '<head>',
  783.             '<title>PDF Reflow conversion</title>',
  784.             '</head>',
  785.             '<body>',
  786.             '<div>']
  787.         for elem in self.elements:
  788.             html.extend(elem.to_html())
  789.         
  790.         html += [
  791.             '</body>',
  792.             '</html>']
  793.         raw = u'\n'.join(html).replace('</strong><strong>', '')
  794.         
  795.         try:
  796.             f = _[1]
  797.             f.write(raw.encode('utf-8'))
  798.         finally:
  799.             pass
  800.  
  801.  
  802.  
  803.