feat(slides):lint table out of canvas

This commit is contained in:
zhanghuanxu
2026-07-17 12:00:33 +08:00
committed by ethan-zhx
parent 76ebd49382
commit 4c0f93bd6a
2 changed files with 103 additions and 1 deletions

View File

@@ -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 :]:

View File

@@ -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(
"""
<presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
<slide xmlns="http://www.larkoffice.com/sml/2.0">
<data>
<table id="score-table" topLeftX="54" topLeftY="238" width="414" height="385">
<tr><td><content><p>Score</p></content></td></tr>
</table>
</data>
</slide>
</presentation>
"""
)
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(
"""
<presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
<slide xmlns="http://www.larkoffice.com/sml/2.0">
<data>
<table id="wide-table" topLeftX="850" topLeftY="80" width="180" height="120">
<tr><td><content><p>Score</p></content></td></tr>
</table>
</data>
</slide>
</presentation>
"""
)
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(
"""
<presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
<slide xmlns="http://www.larkoffice.com/sml/2.0">
<data>
<table id="inside-table" topLeftX="40" topLeftY="120" width="880" height="360">
<tr><td><content><p>Score</p></content></td></tr>
</table>
</data>
</slide>
</presentation>
"""
)
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(
"""