Deck generator: honor output.render_standard_sections, fix principles overlap
Two bugs flagged on the MySQL HeatWave HA example (2026-04-25 round 5): 1. ``render_standard_sections: false`` under the ``output:`` block was silently ignored — the generator only checked the top-level key. Result: every spec that put the flag where the docs suggest got the full standard deck PLUS the custom slide, producing a duplicated "Architecture Overview" + dedicated diagram slide. Now the generator honors both locations. 2. Architecture Principles slide allocated only 0.4" per bullet — exactly one 11pt line. Wrapping principle summaries (e.g. D-03 Use-Case Fit's 125-char text) overflowed into the next bullet's slot, visually overlapping D-02. Diego: "D-03 esta superpuesto a D-02, lo mismo pasa en otras columnas". Bumped per-item height to 0.95" — fits 2-3 wrapped lines with breathing room. 3. PPTX rasterizer (oci_pptx_render.py) gained word-wrap so the local PNG preview matches what real PowerPoint draws. Without it, long text rendered on a single line off the column and the principles fix looked broken in preview when it was actually fine in PowerPoint. Persistent: all three fixes apply to every deck the skill generates. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1516,6 +1516,17 @@ class OCIDeckGenerator(OraclePresBase):
|
|||||||
col_x = self.MARGIN
|
col_x = self.MARGIN
|
||||||
col_width = Inches(4)
|
col_width = Inches(4)
|
||||||
|
|
||||||
|
# Per-item height was Inches(0.4) — fits exactly one 11pt line.
|
||||||
|
# ECAL principle summaries routinely wrap to 2-3 lines (e.g.
|
||||||
|
# "D-03 Use-Case Fit — Map workloads into applicable use-cases.
|
||||||
|
# Architectural simplicity and operational success come from
|
||||||
|
# re-use") which then overflowed into the next bullet's slot.
|
||||||
|
# Diego flagged 2026-04-25: "D-03 esta superpuesto a D-02".
|
||||||
|
# Bumped to 0.95" per item — fits 2-3 wrapped lines with
|
||||||
|
# breathing room. With 3 items per category there's ample
|
||||||
|
# vertical room (3 * 0.95" = 2.85" out of ~5.5" working area).
|
||||||
|
ITEM_H = Inches(0.95)
|
||||||
|
|
||||||
for category in ["design", "deployment", "service"]:
|
for category in ["design", "deployment", "service"]:
|
||||||
items = principles.get(category, [])
|
items = principles.get(category, [])
|
||||||
if not items:
|
if not items:
|
||||||
@@ -1528,7 +1539,7 @@ class OCIDeckGenerator(OraclePresBase):
|
|||||||
color=Colors.TEAL,
|
color=Colors.TEAL,
|
||||||
)
|
)
|
||||||
|
|
||||||
item_y = y + Inches(0.4)
|
item_y = y + Inches(0.45)
|
||||||
for item in items:
|
for item in items:
|
||||||
pid = item.get("id", "")
|
pid = item.get("id", "")
|
||||||
name = item.get("name", "")
|
name = item.get("name", "")
|
||||||
@@ -1542,10 +1553,10 @@ class OCIDeckGenerator(OraclePresBase):
|
|||||||
label += f" — {summary}"
|
label += f" — {summary}"
|
||||||
self._add_textbox(
|
self._add_textbox(
|
||||||
slide, col_x + Inches(0.1), item_y,
|
slide, col_x + Inches(0.1), item_y,
|
||||||
col_width - Inches(0.1), Inches(0.4),
|
col_width - Inches(0.1), ITEM_H,
|
||||||
text=f"• {label}", font_size=11,
|
text=f"• {label}", font_size=11,
|
||||||
)
|
)
|
||||||
item_y += Inches(0.4)
|
item_y += ITEM_H
|
||||||
|
|
||||||
col_x += Inches(4.2)
|
col_x += Inches(4.2)
|
||||||
|
|
||||||
@@ -2297,7 +2308,15 @@ class OCIDeckGenerator(OraclePresBase):
|
|||||||
title=pick(s, "title", default="Engagement Summary"),
|
title=pick(s, "title", default="Engagement Summary"),
|
||||||
)
|
)
|
||||||
|
|
||||||
render_standard_sections = spec.get("render_standard_sections", True)
|
# The flag is documented in two places (top-level OR under
|
||||||
|
# `output:`) — earlier specs put it under `output:` to live
|
||||||
|
# alongside the format selector. Honor both locations so a
|
||||||
|
# spec written either way works.
|
||||||
|
output_block = spec.get("output") or {}
|
||||||
|
render_standard_sections = spec.get(
|
||||||
|
"render_standard_sections",
|
||||||
|
output_block.get("render_standard_sections", True),
|
||||||
|
)
|
||||||
|
|
||||||
def render_custom_slide(item: dict):
|
def render_custom_slide(item: dict):
|
||||||
slide_type = item.get("type")
|
slide_type = item.get("type")
|
||||||
|
|||||||
@@ -539,11 +539,38 @@ class PPTXSlideRenderer:
|
|||||||
text_right = rect[2] - inset_r
|
text_right = rect[2] - inset_r
|
||||||
text_bottom = rect[3] - inset_b
|
text_bottom = rect[3] - inset_b
|
||||||
box_w = max(text_right - text_left, 1)
|
box_w = max(text_right - text_left, 1)
|
||||||
|
# Word-wrap to box_w. PowerPoint does this natively when
|
||||||
|
# text_frame.word_wrap=true (the default for added textboxes);
|
||||||
|
# without it the rasterizer preview shows long bullets running
|
||||||
|
# off the column and visually overlapping siblings — Diego
|
||||||
|
# flagged 2026-04-25 on the Architecture Principles slide.
|
||||||
|
def _wrap(text: str, font: ImageFont.ImageFont, max_w: int) -> str:
|
||||||
|
wrapped_lines: list[str] = []
|
||||||
|
for raw_line in text.split("\n"):
|
||||||
|
if not raw_line:
|
||||||
|
wrapped_lines.append("")
|
||||||
|
continue
|
||||||
|
words = raw_line.split(" ")
|
||||||
|
current = ""
|
||||||
|
for word in words:
|
||||||
|
candidate = (current + " " + word).strip()
|
||||||
|
cand_w = self.draw.textbbox((0, 0), candidate, font=font)[2]
|
||||||
|
if cand_w <= max_w or not current:
|
||||||
|
current = candidate
|
||||||
|
else:
|
||||||
|
wrapped_lines.append(current)
|
||||||
|
current = word
|
||||||
|
if current:
|
||||||
|
wrapped_lines.append(current)
|
||||||
|
return "\n".join(wrapped_lines) if wrapped_lines else text
|
||||||
|
|
||||||
layouts = []
|
layouts = []
|
||||||
total_h = 0
|
total_h = 0
|
||||||
for paragraph in paragraphs:
|
for paragraph in paragraphs:
|
||||||
font = _font(paragraph["size_px"], paragraph["bold"])
|
font = _font(paragraph["size_px"], paragraph["bold"])
|
||||||
bbox = self.draw.multiline_textbbox((0, 0), paragraph["text"], font=font, spacing=2, align="left")
|
wrapped_text = _wrap(paragraph["text"], font, box_w)
|
||||||
|
paragraph["text"] = wrapped_text
|
||||||
|
bbox = self.draw.multiline_textbbox((0, 0), wrapped_text, font=font, spacing=2, align="left")
|
||||||
width = bbox[2] - bbox[0]
|
width = bbox[2] - bbox[0]
|
||||||
height = bbox[3] - bbox[1]
|
height = bbox[3] - bbox[1]
|
||||||
layouts.append((paragraph, font, width, height))
|
layouts.append((paragraph, font, width, height))
|
||||||
|
|||||||
Reference in New Issue
Block a user