@@ -961,13 +961,13 @@ def replace_arrow(match):
961961# Content height weights (approximate units where 1 unit ~ 30px)
962962CONTENT_WEIGHTS = {
963963 # Recalibrated based on visual inspection of actual slides
964- # A slide with H1 + 2 callout boxes + 4 list items should estimate ~10 units (uses ~50 % of slide)
964+ # A slide with H1 + 2 callout boxes + 4 list items should estimate ~14 units (uses ~70 % of slide)
965965 "h1" : 2.0 ,
966966 "h2" : 1.8 ,
967967 "h3" : 1.5 ,
968968 "paragraph_per_50_chars" : 0.4 , # Reduced - text wraps efficiently
969969 "list_item" : 0.7 , # Reduced - list items are compact
970- "callout_box_base" : 2 .5 , # Reduced - callout overhead is smaller than estimated
970+ "callout_box_base" : 4 .5 , # Each callout box ~80-90px (title + padding + content)
971971 "callout_content_per_50_chars" : 0.3 ,
972972 "code_block_line" : 0.6 , # Code lines are relatively compact
973973 "table_header" : 1.2 ,
@@ -998,6 +998,21 @@ def replace_arrow(match):
998998}
999999
10001000
1001+ def _estimate_callout_content_height (slide_content : str ) -> float :
1002+ """Estimate additional height from text content inside callout boxes."""
1003+ total = 0.0
1004+ callout_pattern = re .compile (
1005+ r'<div\s+class="[^"]*(?:note|warning|tip|example|definition|important|callout)-box[^"]*"'
1006+ r"[^>]*>(.*?)</div>" ,
1007+ re .DOTALL ,
1008+ )
1009+ for match in callout_pattern .finditer (slide_content ):
1010+ content = match .group (1 )
1011+ text_only = re .sub (r"<[^>]+>" , "" , content ).strip ()
1012+ total += (len (text_only ) / 50 ) * CONTENT_WEIGHTS ["callout_content_per_50_chars" ]
1013+ return total
1014+
1015+
10011016def compute_available_code_lines (slide_content : str , default_max : int = 20 ) -> int :
10021017 """
10031018 Compute max code lines for a slide based on other content and scale class.
@@ -1020,6 +1035,7 @@ def compute_available_code_lines(slide_content: str, default_max: int = 20) -> i
10201035 other_height += CONTENT_WEIGHTS ["h1" ]
10211036
10221037 callout_height = metrics ["callout_count" ] * CONTENT_WEIGHTS ["callout_box_base" ]
1038+ callout_height += _estimate_callout_content_height (slide_content )
10231039 list_height = metrics ["list_items" ] * CONTENT_WEIGHTS ["list_item" ]
10241040
10251041 if metrics ["has_two_column" ] and metrics ["callout_count" ] >= 2 :
@@ -1314,6 +1330,32 @@ def inject_scale_class(slide_content: str, scale_class: str) -> str:
13141330 return "\n " .join (lines )
13151331
13161332
1333+ def inject_layout_classes (slide_content : str , classes : list ) -> str :
1334+ """Inject layout helper classes into the slide's _class directive."""
1335+ if not classes :
1336+ return slide_content
1337+
1338+ existing_class = re .search (r"<!--\s*_class:\s*([^>]*)\s*-->" , slide_content )
1339+ if existing_class :
1340+ old_directive = existing_class .group (0 )
1341+ old_classes = existing_class .group (1 ).strip ()
1342+ new_classes = [c for c in classes if c not in old_classes ]
1343+ if not new_classes :
1344+ return slide_content
1345+ new_directive = f"<!-- _class: { old_classes } { ' ' .join (new_classes )} -->"
1346+ return slide_content .replace (old_directive , new_directive )
1347+
1348+ lines = slide_content .split ("\n " )
1349+ insert_idx = 0
1350+ for i , line in enumerate (lines ):
1351+ if line .strip ():
1352+ insert_idx = i
1353+ break
1354+
1355+ lines .insert (insert_idx , f"<!-- _class: { ' ' .join (classes )} -->" )
1356+ return "\n " .join (lines )
1357+
1358+
13171359def analyze_and_warn_slides (content , filename = "unknown" ):
13181360 """
13191361 Analyze all slides in content and generate warnings.
@@ -1354,6 +1396,13 @@ def analyze_and_warn_slides(content, filename="unknown"):
13541396 )
13551397 slide = inject_scale_class (slide , scale_class )
13561398
1399+ layout_classes = []
1400+ if metrics ["callout_count" ] > 0 and metrics ["has_code_block" ]:
1401+ layout_classes .append (f"has-callouts-{ min (metrics ['callout_count' ], 3 )} " )
1402+ layout_classes .append ("has-code-block" )
1403+ if layout_classes :
1404+ slide = inject_layout_classes (slide , layout_classes )
1405+
13571406 modified_parts .append (slide )
13581407
13591408 # Rejoin with slide separators
0 commit comments