(texts, grid, return_unmatched_texts=False)
Fit text boxes <texts> into the grid <grid>, always choosing the grid cell with the most intersection for a 
    text box.
    Return a data table with the same dimensions as <grid>, each  cell containing a list of text boxes
    (or an empty list).
Fit text boxes <texts> into the grid <grid>, always choosing the grid cell with the most intersection for a 
    text box.
    Return a data table with the same dimensions as <grid>, each  cell containing a list of text boxes
    (or an empty list).
Fit text boxes <texts > into the grid <grid > always choosing the grid cell with the most intersection for a text box . Return a data table with the same dimensions as <grid > each cell containing a list of text boxes ( or an empty list ) .
def fit_texts_into_grid(texts, grid, return_unmatched_texts=False):
    """
    Fit text boxes <texts> into the grid <grid>, always choosing the grid cell with the most intersection for a 
    text box.
    Return a data table with the same dimensions as <grid>, each  cell containing a list of text boxes
    (or an empty list).
    """
    n_rows = len(grid)
    
    if n_rows == 0:
        raise ValueError("invalid grid: grid has no rows")
     
    n_cols = len(grid[0])
        
    if n_cols == 0:
        raise ValueError("invalid grid: grid has no columns")
    
    # iterate through the textblocks of this page
    texts_in_cells = defaultdict(lambda: defaultdict(list))
    unmatched_texts = []
    for t in texts:
        t_rect = rect_from_text(t)   # rectangle of the textbox
        
        # find out the cells with which this textbox rectangle intersects
        cell_isects = []
        for i, row in enumerate(grid):
            first_cell_rect = row[0]
            row_top = first_cell_rect[0][1]
            row_bottom = first_cell_rect[1][1]
            
            if row_top <= t['top'] <= row_bottom \
                    or row_top <= t['bottom'] <= row_bottom \
                    or (t['top'] <= row_top and t['bottom'] >= row_bottom):
                for j, cell_rect in enumerate(row):
                    c_l, c_t = cell_rect[0]
                    c_r, c_b = cell_rect[1]
                    if c_l <= t['left'] <= c_r or c_l <= t['right'] <= c_r \
                            or (t['left'] <= c_l and t['right'] >= c_r):
                        isect = rectintersect(cell_rect, t_rect, norm_intersect_area='b')
                        assert isect is not None
                        if isect > 0:  # only "touch" is not enough
                            cell_isects.append(((i, j), isect, rectcenter_dist(t_rect, cell_rect)))
        
        if len(cell_isects) > 0:
            # find out the cell with most overlap
            max_isect_val = max([x[1] for x in cell_isects])   # best overlap value
            # could be several "best overlap" cells -> choose the closest
            best_isect = sorted([x for x in cell_isects if x[1] == max_isect_val], key=lambda x: x[2])[0]
            best_i, best_j = best_isect[0]
            
            # add this textblock to the table at the found cell index
            texts_in_cells[best_i][best_j].append(t)
        else:  # no intersection at all
            unmatched_texts.append(t)
    
    # generate a table with the texts
    table = []
    n_texts_in_cells = 0
    for i in range(n_rows):
        row = []
        for j in range(n_cols):
            ts = texts_in_cells.get(i, {}).get(j, [])
            row.append(ts)
            n_texts_in_cells += len(ts)
        table.append(row)
    
    assert len(texts) == n_texts_in_cells + len(unmatched_texts)
    
    if return_unmatched_texts:
        return table, unmatched_texts
    else:
        return table
def fit_texts_into_grid ( texts , grid , return_unmatched_texts = False ) : n_rows = len ( grid ) if n_rows == 0 : raise ValueError ( "invalid grid: grid has no rows" ) n_cols = len ( grid [ 0 ] ) if n_cols == 0 : raise ValueError ( "invalid grid: grid has no columns" ) # iterate through the textblocks of this page texts_in_cells = defaultdict ( lambda : defaultdict ( list ) ) unmatched_texts = [ ] for t in texts : t_rect = rect_from_text ( t ) # rectangle of the textbox # find out the cells with which this textbox rectangle intersects cell_isects = [ ] for i , row in enumerate ( grid ) : first_cell_rect = row [ 0 ] row_top = first_cell_rect [ 0 ] [ 1 ] row_bottom = first_cell_rect [ 1 ] [ 1 ] if row_top <= t [ 'top' ] <= row_bottom or row_top <= t [ 'bottom' ] <= row_bottom or ( t [ 'top' ] <= row_top and t [ 'bottom' ] >= row_bottom ) : for j , cell_rect in enumerate ( row ) : c_l , c_t = cell_rect [ 0 ] c_r , c_b = cell_rect [ 1 ] if c_l <= t [ 'left' ] <= c_r or c_l <= t [ 'right' ] <= c_r or ( t [ 'left' ] <= c_l and t [ 'right' ] >= c_r ) : isect = rectintersect ( cell_rect , t_rect , norm_intersect_area = 'b' ) assert isect is not None if isect > 0 : # only "touch" is not enough cell_isects . append ( ( ( i , j ) , isect , rectcenter_dist ( t_rect , cell_rect ) ) ) if len ( cell_isects ) > 0 : # find out the cell with most overlap max_isect_val = max ( [ x [ 1 ] for x in cell_isects ] ) # best overlap value # could be several "best overlap" cells -> choose the closest best_isect = sorted ( [ x for x in cell_isects if x [ 1 ] == max_isect_val ] , key = lambda x : x [ 2 ] ) [ 0 ] best_i , best_j = best_isect [ 0 ] # add this textblock to the table at the found cell index texts_in_cells [ best_i ] [ best_j ] . append ( t ) else : # no intersection at all unmatched_texts . append ( t ) # generate a table with the texts table = [ ] n_texts_in_cells = 0 for i in range ( n_rows ) : row = [ ] for j in range ( n_cols ) : ts = texts_in_cells . get ( i , { } ) . get ( j , [ ] ) row . append ( ts ) n_texts_in_cells += len ( ts ) table . append ( row ) assert len ( texts ) == n_texts_in_cells + len ( unmatched_texts ) if return_unmatched_texts : return table , unmatched_texts else : return table