Download docs/presentation/pptlib.py from kimjunhan/vkgs-sim: direct link, hf CLI and curl.
- Browser
- Download file 6.79 kB
-
https://huggingface.co/kimjunhan/vkgs-sim/resolve/main/docs/presentation/pptlib.py
- Command line
-
hf download hf://kimjunhan/vkgs-sim/docs/presentation/pptlib.py
-
curl -L -o pptlib.py https://huggingface.co/kimjunhan/vkgs-sim/resolve/main/docs/presentation/pptlib.py
6.79 kB
| 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) | |