File size: 6,790 Bytes
ceea884 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | import copy
import json
from PIL import Image
from pptx import Presentation
from pptx.chart.data import CategoryChartData
from pptx.dml.color import RGBColor
from pptx.enum.chart import XL_CHART_TYPE, XL_LABEL_POSITION
from pptx.enum.shapes import MSO_SHAPE
from pptx.enum.text import MSO_ANCHOR, PP_ALIGN
from pptx.oxml.ns import qn
from pptx.util import Inches, Pt
A = "/tmp/claude-1000/-home-mm-Downloads-vkgs-sim/8bf0e66c-975d-4257-af27-61bc25ee4316/scratchpad/deck/"
NAVY, BLUE, LIGHT, PALE, GRAY, ORANGE, WHITE = (RGBColor(0x1F, 0x3B, 0x70), RGBColor(0x44, 0x72, 0xC4), RGBColor(0xDC, 0xE6, 0xF4),
RGBColor(0xF2, 0xF5, 0xFA), RGBColor(0x40, 0x40, 0x40), RGBColor(0xED, 0x7D, 0x31),
RGBColor(0xFF, 0xFF, 0xFF))
FONT = "맑은 고딕"
KEEP = {"슬라이드 번호 개체 틀 1", "제목 1", "그룹 6", "TextBox 9", "TextBox 4"}
def shape(sl, name):
for sh in sl.shapes:
if sh.name == name:
return sh
raise KeyError(name)
def set_runs_text(tf, text):
"""첫 문단 첫 run 의 서식으로 text 만 남긴다."""
p0 = tf.paragraphs[0]
for p in tf.paragraphs[1:]:
p._p.getparent().remove(p._p)
runs = p0.runs
if not runs:
p0.text = text
return
runs[0].text = text
for r in runs[1:]:
r._r.getparent().remove(r._r)
def set_paragraphs(tf, items):
"""첫 문단을 본으로 문단마다 복사 (글머리표·간격 유지)."""
p0 = tf.paragraphs[0]._p
for p in tf.paragraphs[1:]:
p._p.getparent().remove(p._p)
parent = p0.getparent()
proto = copy.deepcopy(p0)
parent.remove(p0)
for it in items:
p = copy.deepcopy(proto)
rs = p.findall(qn("a:r"))
for r in rs[1:]:
p.remove(r)
if rs:
rs[0].find(qn("a:t")).text = it
parent.append(p)
def body(sl, tab, heading, bullets):
for sh in list(sl.shapes):
if sh.name not in KEEP:
sh._element.getparent().remove(sh._element)
set_runs_text(shape(sl, "제목 1").text_frame, "VKGS-Sim")
set_runs_text(shape(sl, "TextBox 4").text_frame, tab)
grp = shape(sl, "그룹 6")
for c in grp.shapes:
if c.has_text_frame and c.text_frame.text.strip():
set_runs_text(c.text_frame, heading)
set_paragraphs(shape(sl, "TextBox 9").text_frame, bullets)
return 1.82 + 0.28 * sum(1 + len(b) // 95 for b in bullets) + 0.15 # 본문 다음 y (12pt, 줄간격 150%)
def notes(sl, text):
sl.notes_slide.notes_text_frame.text = text
def tb(sl, x, y, w, h, text, size=12, bold=False, color=GRAY, align=PP_ALIGN.LEFT, anchor=MSO_ANCHOR.TOP, font=FONT):
t = sl.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h))
tf = t.text_frame
tf.word_wrap = True
tf.margin_left = tf.margin_right = Inches(0.04)
tf.margin_top = tf.margin_bottom = Inches(0.02)
tf.vertical_anchor = anchor
for i, line in enumerate(text.split("\n")):
p = tf.paragraphs[0] if i == 0 else tf.add_paragraph()
p.alignment = align
r = p.add_run()
r.text = line
r.font.size, r.font.bold, r.font.color.rgb, r.font.name = Pt(size), bold, color, font
return t
def box(sl, x, y, w, h, text, fill=LIGHT, color=GRAY, size=12, bold=False, line=None, align=PP_ALIGN.CENTER, title=None):
s = sl.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, Inches(x), Inches(y), Inches(w), Inches(h))
s.adjustments[0] = 0.08
s.fill.solid()
s.fill.fore_color.rgb = fill
if line:
s.line.color.rgb = line
s.line.width = Pt(1.25)
else:
s.line.fill.background()
s.shadow.inherit = False
tf = s.text_frame
tf.word_wrap = True
tf.margin_left = tf.margin_right = Inches(0.1)
tf.margin_top = tf.margin_bottom = Inches(0.06)
tf.vertical_anchor = MSO_ANCHOR.MIDDLE if title is None else MSO_ANCHOR.TOP
lines = ([(title, True, size + 1)] if title else []) + [(l, bold, size) for l in text.split("\n") if l]
for i, (l, b, sz) in enumerate(lines):
p = tf.paragraphs[0] if i == 0 else tf.add_paragraph()
p.alignment = PP_ALIGN.CENTER if (title and i == 0) else align
p.space_after = Pt(3)
r = p.add_run()
r.text = l
r.font.size, r.font.bold, r.font.color.rgb, r.font.name = Pt(sz), b, color, FONT
return s
def arrow(sl, x, y, w=0.36, h=0.24, color=BLUE):
s = sl.shapes.add_shape(MSO_SHAPE.RIGHT_ARROW, Inches(x), Inches(y), Inches(w), Inches(h))
s.fill.solid()
s.fill.fore_color.rgb = color
s.line.fill.background()
s.shadow.inherit = False
return s
def image(sl, path, x, y, w=None, h=None):
iw, ih = Image.open(path).size
if w and h: # 상자 안에 맞춘다 (contain), 가운데
s = min(w / iw, h / ih)
ww, hh = iw * s, ih * s
return sl.shapes.add_picture(path, Inches(x + (w - ww) / 2), Inches(y + (h - hh) / 2), Inches(ww), Inches(hh))
if w:
return sl.shapes.add_picture(path, Inches(x), Inches(y), Inches(w), Inches(w * ih / iw))
return sl.shapes.add_picture(path, Inches(x), Inches(y), Inches(h * iw / ih), Inches(h))
def table(sl, x, y, w, rows, colw, size=11, rowh=0.3, align_right=()):
t = sl.shapes.add_table(len(rows), len(rows[0]), Inches(x), Inches(y), Inches(w), Inches(rowh * len(rows))).table
for j, cw in enumerate(colw):
t.columns[j].width = Inches(w * cw)
for i, row in enumerate(rows):
t.rows[i].height = Inches(rowh)
for j, v in enumerate(row):
c = t.cell(i, j)
c.margin_left = c.margin_right = Inches(0.08)
c.margin_top = c.margin_bottom = Inches(0.03)
c.vertical_anchor = MSO_ANCHOR.MIDDLE
c.fill.solid()
c.fill.fore_color.rgb = NAVY if i == 0 else (PALE if i % 2 == 0 else WHITE)
tf = c.text_frame
tf.word_wrap = True
p = tf.paragraphs[0]
p.text = ""
p.alignment = PP_ALIGN.RIGHT if (j in align_right and i > 0) else PP_ALIGN.LEFT
r = p.add_run()
r.text = str(v)
r.font.size, r.font.name = Pt(size), FONT
r.font.bold = i == 0
r.font.color.rgb = WHITE if i == 0 else GRAY
return t
def kpi(sl, x, y, w, num, label, color=NAVY):
s = box(sl, x, y, w, 1.05, "", fill=PALE, line=LIGHT)
tb(sl, x + 0.1, y + 0.08, w - 0.2, 0.55, num, size=26, bold=True, color=color, align=PP_ALIGN.CENTER)
tb(sl, x + 0.1, y + 0.64, w - 0.2, 0.35, label, size=11, color=GRAY, align=PP_ALIGN.CENTER)
return s
def caption(sl, x, y, w, text):
return tb(sl, x, y, w, 0.32, text, size=12, bold=True, color=GRAY, align=PP_ALIGN.CENTER)
|