diff --git a/core/magazine.py b/core/magazine.py index 57d53a1..7a25bd6 100644 --- a/core/magazine.py +++ b/core/magazine.py @@ -126,11 +126,17 @@ class Block: # (clean_up_regions). Part of the model so every path that rebuilds blocks keeps it -- and does # not crash on it (drawing a region did, "unexpected keyword argument 'original_text'"). original_text: str = "" + # box_manual: the reviewer placed or resized this box, so snap_text_boxes never moves it. + # box_snapped: snap_text_boxes fitted it to its text. Both are model fields for the same + # reason as original_text: every path that rebuilds blocks with Block(**b) must accept them. + box_manual: bool = False + box_snapped: bool = False def to_json(self) -> dict[str, Any]: data = asdict(self) - if not data["original_text"]: - del data["original_text"] + for optional in ("original_text", "box_manual", "box_snapped"): + if not data[optional]: + del data[optional] return data diff --git a/tests/test_magazine.py b/tests/test_magazine.py index c4f7952..11a88e0 100644 --- a/tests/test_magazine.py +++ b/tests/test_magazine.py @@ -2747,3 +2747,23 @@ class SnapTextBoxesTests(unittest.TestCase): self.assertEqual(snap_text_boxes(self.dir, layout)[1], []) (self.dir / "pages" / "0001.png").unlink() self.assertEqual(snap_text_boxes(self.dir, self.layout([("a", [60, 60, 250, 200], {})]))[1], []) + + +class BoxFlagsSurviveRebuildTests(unittest.TestCase): + def test_snapped_and_hand_placed_flags_round_trip_through_block(self): + """Regression: a saved box_snapped/box_manual key crashed every Block(**b) rebuild + ("unexpected keyword argument 'box_snapped'") and so a page re-OCR.""" + from core.magazine import Block + raw = {"id": "a", "page": 1, "bbox": [0, 0, 1, 1], "type": "body", "text": "t", + "box_snapped": True, "box_manual": True} + self.assertEqual(Block(**raw).to_json()["box_snapped"], True) + plain = Block(id="a", page=1, bbox=[0, 0, 1, 1], type="body", text="t").to_json() + self.assertNotIn("box_snapped", plain) + self.assertNotIn("box_manual", plain) + + def test_a_snapped_layout_can_be_rebuilt_by_the_semantic_layout_path(self): + from core.magazine import Block, snap_text_boxes + import tempfile, pathlib + layout = {"pages": [{"page": 1, "width": 100, "height": 100}], "images": [], + "blocks": [{"id": "a", "page": 1, "bbox": [0, 0, 10, 10], "type": "body", "text": "t", "box_snapped": True}]} + self.assertEqual([Block(**b).id for b in layout["blocks"]], ["a"])