Column-Aware Reading Order (XY-Cut)

element_parse.reading_order is a flat top-to-bottom / left-to-right sort — it interleaves columns on any multi-column page (the well-documented naive-sort failure: it reads row 1 of column A, then row 1 of column B, then row 2 of A …). reading_flow recovers the correct order with recursive XY-cut: it repeatedly splits the boxes at the widest whitespace valley (a vertical gutter → columns, a horizontal gutter → rows / blocks), so a two-column layout is read down column A fully, then column B.

The public flattener is named flow_order to sit beside — not shadow — element_parse.reading_order; it returns the same index-tagged element contract, so it is a drop-in column-aware upgrade. Pure-stdlib geometry over plain box dicts (no image, no OCR engine); reuses table_grid_fill’s box-bounds reader. Imports no PySide6.

Headless API

from je_auto_control import flow_order, xy_cut, to_blocks

# two columns, two rows each: reads A1, A2, B1, B2 — not A1, B1, A2, B2
for element in flow_order(ocr_boxes, min_gap=12):
    print(element["index"], element["text"])

tree = xy_cut(ocr_boxes, min_gap=12)     # {type, axis, children|boxes}
blocks = to_blocks(tree)                  # leaf blocks in reading order

flow_order returns the boxes in column-aware reading order, each tagged with an index. xy_cut returns the recursive region tree (each node is a split on axis "x" / "y" or a leaf of boxes). to_blocks flattens the tree to its leaf blocks in order. min_gap is the smallest whitespace valley treated as a column / row break.

Executor commands

AC_flow_order (boxes / min_gap{count, elements}) and AC_xy_cut (boxes / min_gap{tree}). They are exposed as the MCP tools ac_flow_order / ac_xy_cut (read-only) and as the Script Builder commands Reading Order (column-aware) / XY-Cut Region Tree under OCR.