(texts)
return lines
Sort text boxes <texts> vertically first and split them into lines. Sort each line horizontally (left to right).
    
    Returns list of lists, each representing a line with text boxes. Empty lines contain empty lists.
Sort text boxes <texts> vertically first and split them into lines. Sort each line horizontally (left to right).
    
    Returns list of lists, each representing a line with text boxes. Empty lines contain empty lists.
Sort text boxes <texts > vertically first and split them into lines . Sort each line horizontally ( left to right ) . Returns list of lists each representing a line with text boxes . Empty lines contain empty lists .
def put_texts_in_lines(texts):
    """
    Sort text boxes <texts> vertically first and split them into lines. Sort each line horizontally (left to right).
    
    Returns list of lists, each representing a line with text boxes. Empty lines contain empty lists.
    """
    if not texts:
        return []
    
    mean_text_height = np.mean([t['bottom'] - t['top'] for t in texts])
    
    sorted_ts = list(sorted(texts, key=lambda x: x['top']))     # sort texts vertically
    # create list of vertical spacings between sorted texts
    text_spacings = [t['top'] - sorted_ts[i - 1]['bottom'] for i, t in enumerate(sorted_ts) if i > 0]
    text_spacings.append(0.0)   # last line
    
    # minimum positive spacing is considered to be the general vertical line spacing
    pos_sp = [v for v in text_spacings if v > 0]
    line_vspace = min(pos_sp) if pos_sp else None
    
    # go through all vertically sorted texts
    lines = []
    cur_line = []
    min_vspace_for_break = -mean_text_height / 2   # texts might overlap vertically. if the overlap is more than half
                                                   # the mean text height, it is considered a line break
    for t, spacing in zip(sorted_ts, text_spacings):
        cur_line.append(t)
        
        if spacing >= min_vspace_for_break:    # this is a line break            
            # add all texts to this line sorted by x-position
            lines.append(list(sorted(cur_line, key=lambda x: x['left'])))
            
            # add some empty line breaks if necessary
            if line_vspace:
                lines.extend([] * int(spacing / line_vspace))
            
            # reset
            cur_line = []            

    assert len(cur_line) == 0    # because last line gets a zero-spacing appended
    assert len(texts) == sum(map(len, lines))     # check if all texts were put into lines
    
    return lines
def put_texts_in_lines ( texts ) : if not texts : return [ ] mean_text_height = np . mean ( [ t [ 'bottom' ] - t [ 'top' ] for t in texts ] ) sorted_ts = list ( sorted ( texts , key = lambda x : x [ 'top' ] ) ) # sort texts vertically # create list of vertical spacings between sorted texts text_spacings = [ t [ 'top' ] - sorted_ts [ i - 1 ] [ 'bottom' ] for i , t in enumerate ( sorted_ts ) if i > 0 ] text_spacings . append ( 0.0 ) # last line # minimum positive spacing is considered to be the general vertical line spacing pos_sp = [ v for v in text_spacings if v > 0 ] line_vspace = min ( pos_sp ) if pos_sp else None # go through all vertically sorted texts lines = [ ] cur_line = [ ] min_vspace_for_break = - mean_text_height / 2 # texts might overlap vertically. if the overlap is more than half # the mean text height, it is considered a line break for t , spacing in zip ( sorted_ts , text_spacings ) : cur_line . append ( t ) if spacing >= min_vspace_for_break : # this is a line break             # add all texts to this line sorted by x-position lines . append ( list ( sorted ( cur_line , key = lambda x : x [ 'left' ] ) ) ) # add some empty line breaks if necessary if line_vspace : lines . extend ( [ ] * int ( spacing / line_vspace ) ) # reset cur_line = [ ] assert len ( cur_line ) == 0 # because last line gets a zero-spacing appended assert len ( texts ) == sum ( map ( len , lines ) ) # check if all texts were put into lines return lines