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)