diff --git a/skills/lark-slides/scripts/xml_text_overlap_lint.py b/skills/lark-slides/scripts/xml_text_overlap_lint.py index 13a3d788a..8b8d9b998 100644 --- a/skills/lark-slides/scripts/xml_text_overlap_lint.py +++ b/skills/lark-slides/scripts/xml_text_overlap_lint.py @@ -867,11 +867,56 @@ def detect_whiteboard_external_overlaps( return issues +def detect_table_out_of_canvas( + elements: list[dict[str, Any]], slide_width: int | float, slide_height: int | float +) -> list[dict[str, Any]]: + issues: list[dict[str, Any]] = [] + for table in (element for element in elements if element["kind"] == "table"): + overflow = { + "left": max(-table["x"], 0), + "top": max(-table["y"], 0), + "right": max(table["x"] + table["width"] - slide_width, 0), + "bottom": max(table["y"] + table["height"] - slide_height, 0), + } + overflow_details = [ + f"{side} by {amount:g}px" for side, amount in overflow.items() if amount > 0 + ] + if not overflow_details: + continue + issues.append( + { + "level": "error", + "code": "table_out_of_canvas", + "elements": [table["id"]], + "canvas": {"width": slide_width, "height": slide_height}, + "bbox": { + "x": table["x"], + "y": table["y"], + "width": table["width"], + "height": table["height"], + }, + "overflow": overflow, + "message": ( + f'table {table["id"]} exceeds the {slide_width:g}x{slide_height:g} canvas ' + f'({", ".join(overflow_details)})' + ), + "hint": ( + "Move the table inside the canvas, reduce table.width/table.height, or split the table across " + "slides." + ), + } + ) + return issues + + def lint_slide( slide_xml: str, slide_number: int, slide_width: int | float = 960, slide_height: int | float = 540 ) -> dict[str, Any]: elements = extract_elements(slide_xml) - issues: list[dict[str, Any]] = detect_whiteboard_external_overlaps(elements, slide_width, slide_height) + issues: list[dict[str, Any]] = [ + *detect_whiteboard_external_overlaps(elements, slide_width, slide_height), + *detect_table_out_of_canvas(elements, slide_width, slide_height), + ] for index, left in enumerate(elements): for right in elements[index + 1 :]: diff --git a/skills/lark-slides/scripts/xml_text_overlap_lint_test.py b/skills/lark-slides/scripts/xml_text_overlap_lint_test.py index 1041b67e8..f601734dd 100644 --- a/skills/lark-slides/scripts/xml_text_overlap_lint_test.py +++ b/skills/lark-slides/scripts/xml_text_overlap_lint_test.py @@ -712,6 +712,63 @@ class XmlTextOverlapLintTest(unittest.TestCase): ) self.assertEqual(result["summary"]["error_count"], 0) + def test_lint_xml_reports_table_bottom_overflow_from_declared_bounds(self) -> None: + result = xml_text_overlap_lint.lint_xml( + """ + + + + + +

Score

+
+
+
+ """ + ) + issue = result["slides"][0]["issues"][0] + self.assertEqual(result["summary"]["error_count"], 1) + self.assertEqual(issue["code"], "table_out_of_canvas") + self.assertEqual(issue["elements"], ["score-table"]) + self.assertEqual(issue["overflow"], {"left": 0, "top": 0, "right": 0, "bottom": 83}) + self.assertEqual(issue["bbox"], {"x": 54, "y": 238, "width": 414, "height": 385}) + + def test_lint_xml_reports_table_right_overflow_from_declared_bounds(self) -> None: + result = xml_text_overlap_lint.lint_xml( + """ + + + + + +

Score

+
+
+
+ """ + ) + issue = result["slides"][0]["issues"][0] + self.assertEqual(result["summary"]["error_count"], 1) + self.assertEqual(issue["code"], "table_out_of_canvas") + self.assertEqual(issue["overflow"], {"left": 0, "top": 0, "right": 70, "bottom": 0}) + + def test_lint_xml_allows_table_with_declared_bounds_inside_canvas(self) -> None: + result = xml_text_overlap_lint.lint_xml( + """ + + + + + +

Score

+
+
+
+ """ + ) + self.assertEqual(result["summary"]["error_count"], 0) + self.assertEqual(result["summary"]["warning_count"], 0) + def test_lint_xml_warns_for_whiteboard_external_boundary_overlap(self) -> None: result = xml_text_overlap_lint.lint_xml( """