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:
root
2026-04-25 22:44:40 -03:00
parent 68d1753a74
commit a0a059887a
2 changed files with 51 additions and 5 deletions

View File

@@ -539,11 +539,38 @@ class PPTXSlideRenderer:
text_right = rect[2] - inset_r
text_bottom = rect[3] - inset_b
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 = []
total_h = 0
for paragraph in paragraphs:
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]
height = bbox[3] - bbox[1]
layouts.append((paragraph, font, width, height))