code: 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
  
file_path: pdftabextract/pdftabextract/textboxes.py
imports: import math
  import numpy
  from pdftabextract.common import update_text_dict_pos, sorted_by_attr, DIRECTION_HORIZONTAL, DIRECTION_VERTICAL, SKEW_X, SKEW_Y
  from pdftabextract.geom import pt, vecrotate
label_desc: Use image node_id 3 for calling a global function with example usage: put_texts_in_lines(texts) and returns: lines, list