@ww_nero/skills 3.0.1 → 3.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/svg_to_pptx.py +204 -164
- package/index.js +2 -2
- package/package.json +16 -16
package/assets/svg_to_pptx.py
CHANGED
|
@@ -1,15 +1,7 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
1
|
# -*- coding: utf-8 -*-
|
|
3
|
-
"""
|
|
4
|
-
将HTML文件中的SVG转换为PPTX
|
|
5
|
-
|
|
6
|
-
注意事项:
|
|
7
|
-
- 对于 icon 元素(如带有 id="icon-*" 的 <g> 元素),
|
|
8
|
-
会先将其独立保存为小的 PNG 图片,然后再插入到 PPTX 中。
|
|
9
|
-
这样可以保证矢量图标在 PowerPoint 中正确显示。
|
|
10
|
-
"""
|
|
11
2
|
import os
|
|
12
3
|
import re
|
|
4
|
+
import math
|
|
13
5
|
import tempfile
|
|
14
6
|
from pptx import Presentation
|
|
15
7
|
from pptx.util import Pt, Emu
|
|
@@ -30,6 +22,46 @@ def hex_to_rgb(hex_color):
|
|
|
30
22
|
hex_color = ''.join([c*2 for c in hex_color])
|
|
31
23
|
return RGBColor(int(hex_color[0:2], 16), int(hex_color[2:4], 16), int(hex_color[4:6], 16))
|
|
32
24
|
|
|
25
|
+
def _overwrite_alpha(srgbClr, alpha_float):
|
|
26
|
+
"""覆盖 srgbClr 下的透明度节点"""
|
|
27
|
+
if srgbClr is None:
|
|
28
|
+
return
|
|
29
|
+
for a in list(srgbClr.findall(qn("a:alpha"))):
|
|
30
|
+
srgbClr.remove(a)
|
|
31
|
+
alpha = OxmlElement('a:alpha')
|
|
32
|
+
alpha.set('val', str(int(alpha_float * 100000)))
|
|
33
|
+
srgbClr.append(alpha)
|
|
34
|
+
|
|
35
|
+
def _clear_fill_elements(parent):
|
|
36
|
+
"""移除已有填充定义,避免冲突"""
|
|
37
|
+
for tag in ('a:solidFill', 'a:gradFill', 'a:noFill'):
|
|
38
|
+
existing = parent.find(qn(tag))
|
|
39
|
+
if existing is not None:
|
|
40
|
+
parent.remove(existing)
|
|
41
|
+
|
|
42
|
+
def _make_grad_stop(pos, hex_color, alpha=None):
|
|
43
|
+
gs = OxmlElement('a:gs')
|
|
44
|
+
gs.set('pos', str(pos))
|
|
45
|
+
srgbClr = OxmlElement('a:srgbClr')
|
|
46
|
+
srgbClr.set('val', hex_color.lstrip('#'))
|
|
47
|
+
if alpha is not None and alpha < 1.0:
|
|
48
|
+
_overwrite_alpha(srgbClr, alpha)
|
|
49
|
+
gs.append(srgbClr)
|
|
50
|
+
return gs
|
|
51
|
+
|
|
52
|
+
def _build_linear_gradient(color1, alpha1, color2, alpha2, angle_deg=0, scaled='1', rot_with_shape='1'):
|
|
53
|
+
gradFill = OxmlElement('a:gradFill')
|
|
54
|
+
gradFill.set('rotWithShape', rot_with_shape)
|
|
55
|
+
gsLst = OxmlElement('a:gsLst')
|
|
56
|
+
gsLst.append(_make_grad_stop(0, color1, alpha1))
|
|
57
|
+
gsLst.append(_make_grad_stop(100000, color2, alpha2))
|
|
58
|
+
lin = OxmlElement('a:lin')
|
|
59
|
+
lin.set('ang', str(int(angle_deg * 60000)))
|
|
60
|
+
lin.set('scaled', scaled)
|
|
61
|
+
gradFill.append(gsLst)
|
|
62
|
+
gradFill.append(lin)
|
|
63
|
+
return gradFill
|
|
64
|
+
|
|
33
65
|
def _catmull_rom_spline(points, segments_per_curve=20):
|
|
34
66
|
"""Catmull-Rom 样条插值,生成平滑曲线点"""
|
|
35
67
|
if len(points) < 2:
|
|
@@ -64,26 +96,7 @@ def set_slide_background_gradient(slide, color1, color2):
|
|
|
64
96
|
else:
|
|
65
97
|
bg.clear()
|
|
66
98
|
bgPr = OxmlElement('p:bgPr')
|
|
67
|
-
gradFill =
|
|
68
|
-
gradFill.set('rotWithShape', '1')
|
|
69
|
-
gsLst = OxmlElement('a:gsLst')
|
|
70
|
-
|
|
71
|
-
def add_stop(pos, hex_c):
|
|
72
|
-
gs = OxmlElement('a:gs')
|
|
73
|
-
gs.set('pos', str(pos))
|
|
74
|
-
srgbClr = OxmlElement('a:srgbClr')
|
|
75
|
-
srgbClr.set('val', hex_c.lstrip('#'))
|
|
76
|
-
gs.append(srgbClr)
|
|
77
|
-
gsLst.append(gs)
|
|
78
|
-
|
|
79
|
-
add_stop(0, color1)
|
|
80
|
-
add_stop(100000, color2)
|
|
81
|
-
|
|
82
|
-
lin = OxmlElement('a:lin')
|
|
83
|
-
lin.set('ang', '2700000')
|
|
84
|
-
lin.set('scaled', '0')
|
|
85
|
-
gradFill.append(gsLst)
|
|
86
|
-
gradFill.append(lin)
|
|
99
|
+
gradFill = _build_linear_gradient(color1, None, color2, None, angle_deg=45, scaled='0')
|
|
87
100
|
bgPr.append(gradFill)
|
|
88
101
|
effectLst = OxmlElement('a:effectLst')
|
|
89
102
|
bgPr.append(effectLst)
|
|
@@ -97,12 +110,7 @@ def set_transparency(shape, alpha_float, is_line=True):
|
|
|
97
110
|
if hasattr(fore_color, '_xFill'):
|
|
98
111
|
elm = fore_color._xFill
|
|
99
112
|
if hasattr(elm, "srgbClr"):
|
|
100
|
-
|
|
101
|
-
for a in clr.findall(qn("a:alpha")):
|
|
102
|
-
clr.remove(a)
|
|
103
|
-
alpha = OxmlElement('a:alpha')
|
|
104
|
-
alpha.set('val', str(int(alpha_float * 100000)))
|
|
105
|
-
clr.append(alpha)
|
|
113
|
+
_overwrite_alpha(elm.srgbClr, alpha_float)
|
|
106
114
|
|
|
107
115
|
def set_shape_transparency(shape, alpha_float):
|
|
108
116
|
"""设置形状填充透明度"""
|
|
@@ -110,53 +118,13 @@ def set_shape_transparency(shape, alpha_float):
|
|
|
110
118
|
solidFill = spPr.find(qn('a:solidFill'))
|
|
111
119
|
if solidFill is not None:
|
|
112
120
|
srgbClr = solidFill.find(qn('a:srgbClr'))
|
|
113
|
-
|
|
114
|
-
for a in srgbClr.findall(qn("a:alpha")):
|
|
115
|
-
srgbClr.remove(a)
|
|
116
|
-
alpha = OxmlElement('a:alpha')
|
|
117
|
-
alpha.set('val', str(int(alpha_float * 100000)))
|
|
118
|
-
srgbClr.append(alpha)
|
|
121
|
+
_overwrite_alpha(srgbClr, alpha_float)
|
|
119
122
|
|
|
120
123
|
def set_shape_gradient_fill(shape, color1, alpha1, color2, alpha2, angle=0):
|
|
121
124
|
"""设置形状渐变填充"""
|
|
122
125
|
spPr = shape._sp.spPr
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if existing is not None:
|
|
126
|
-
spPr.remove(existing)
|
|
127
|
-
|
|
128
|
-
gradFill = OxmlElement('a:gradFill')
|
|
129
|
-
gradFill.set('rotWithShape', '1')
|
|
130
|
-
gsLst = OxmlElement('a:gsLst')
|
|
131
|
-
|
|
132
|
-
gs1 = OxmlElement('a:gs')
|
|
133
|
-
gs1.set('pos', '0')
|
|
134
|
-
srgbClr1 = OxmlElement('a:srgbClr')
|
|
135
|
-
srgbClr1.set('val', color1.lstrip('#'))
|
|
136
|
-
if alpha1 < 1.0:
|
|
137
|
-
alpha_elem1 = OxmlElement('a:alpha')
|
|
138
|
-
alpha_elem1.set('val', str(int(alpha1 * 100000)))
|
|
139
|
-
srgbClr1.append(alpha_elem1)
|
|
140
|
-
gs1.append(srgbClr1)
|
|
141
|
-
gsLst.append(gs1)
|
|
142
|
-
|
|
143
|
-
gs2 = OxmlElement('a:gs')
|
|
144
|
-
gs2.set('pos', '100000')
|
|
145
|
-
srgbClr2 = OxmlElement('a:srgbClr')
|
|
146
|
-
srgbClr2.set('val', color2.lstrip('#'))
|
|
147
|
-
if alpha2 < 1.0:
|
|
148
|
-
alpha_elem2 = OxmlElement('a:alpha')
|
|
149
|
-
alpha_elem2.set('val', str(int(alpha2 * 100000)))
|
|
150
|
-
srgbClr2.append(alpha_elem2)
|
|
151
|
-
gs2.append(srgbClr2)
|
|
152
|
-
gsLst.append(gs2)
|
|
153
|
-
|
|
154
|
-
lin = OxmlElement('a:lin')
|
|
155
|
-
lin.set('ang', str(int(angle * 60000)))
|
|
156
|
-
lin.set('scaled', '1')
|
|
157
|
-
|
|
158
|
-
gradFill.append(gsLst)
|
|
159
|
-
gradFill.append(lin)
|
|
126
|
+
_clear_fill_elements(spPr)
|
|
127
|
+
gradFill = _build_linear_gradient(color1, alpha1, color2, alpha2, angle)
|
|
160
128
|
spPr.append(gradFill)
|
|
161
129
|
|
|
162
130
|
def set_line_transparency(connector, alpha_float):
|
|
@@ -170,12 +138,7 @@ def set_line_transparency(connector, alpha_float):
|
|
|
170
138
|
solidFill = ln.find(qn('a:solidFill'))
|
|
171
139
|
if solidFill is not None:
|
|
172
140
|
srgbClr = solidFill.find(qn('a:srgbClr'))
|
|
173
|
-
|
|
174
|
-
for a in srgbClr.findall(qn("a:alpha")):
|
|
175
|
-
srgbClr.remove(a)
|
|
176
|
-
alpha = OxmlElement('a:alpha')
|
|
177
|
-
alpha.set('val', str(int(alpha_float * 100000)))
|
|
178
|
-
srgbClr.append(alpha)
|
|
141
|
+
_overwrite_alpha(srgbClr, alpha_float)
|
|
179
142
|
|
|
180
143
|
def set_line_stroke_transparency(shape, alpha_float):
|
|
181
144
|
"""设置形状边框透明度"""
|
|
@@ -187,55 +150,19 @@ def set_line_stroke_transparency(shape, alpha_float):
|
|
|
187
150
|
solidFill = ln.find(qn('a:solidFill'))
|
|
188
151
|
if solidFill is not None:
|
|
189
152
|
srgbClr = solidFill.find(qn('a:srgbClr'))
|
|
190
|
-
|
|
191
|
-
for a in srgbClr.findall(qn("a:alpha")):
|
|
192
|
-
srgbClr.remove(a)
|
|
193
|
-
alpha = OxmlElement('a:alpha')
|
|
194
|
-
alpha.set('val', str(int(alpha_float * 100000)))
|
|
195
|
-
srgbClr.append(alpha)
|
|
153
|
+
_overwrite_alpha(srgbClr, alpha_float)
|
|
196
154
|
|
|
197
155
|
def apply_gradient_to_text(run, color1, color2):
|
|
198
156
|
"""应用渐变到文本"""
|
|
199
157
|
rPr = run._r.get_or_add_rPr()
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
if existing is not None:
|
|
203
|
-
rPr.remove(existing)
|
|
204
|
-
|
|
205
|
-
gradFill = OxmlElement('a:gradFill')
|
|
206
|
-
gradFill.set('rotWithShape', '1')
|
|
207
|
-
|
|
208
|
-
gsLst = OxmlElement('a:gsLst')
|
|
209
|
-
|
|
210
|
-
gs1 = OxmlElement('a:gs')
|
|
211
|
-
gs1.set('pos', '0')
|
|
212
|
-
srgb1 = OxmlElement('a:srgbClr')
|
|
213
|
-
srgb1.set('val', color1.lstrip('#'))
|
|
214
|
-
gs1.append(srgb1)
|
|
215
|
-
gsLst.append(gs1)
|
|
216
|
-
|
|
217
|
-
gs2 = OxmlElement('a:gs')
|
|
218
|
-
gs2.set('pos', '100000')
|
|
219
|
-
srgb2 = OxmlElement('a:srgbClr')
|
|
220
|
-
srgb2.set('val', color2.lstrip('#'))
|
|
221
|
-
gs2.append(srgb2)
|
|
222
|
-
gsLst.append(gs2)
|
|
223
|
-
|
|
224
|
-
lin = OxmlElement('a:lin')
|
|
225
|
-
lin.set('ang', '0')
|
|
226
|
-
lin.set('scaled', '1')
|
|
227
|
-
|
|
228
|
-
gradFill.append(gsLst)
|
|
229
|
-
gradFill.append(lin)
|
|
158
|
+
_clear_fill_elements(rPr)
|
|
159
|
+
gradFill = _build_linear_gradient(color1, 1.0, color2, 1.0, 0)
|
|
230
160
|
rPr.insert(0, gradFill)
|
|
231
161
|
|
|
232
162
|
def set_text_color_xml(run, hex_color):
|
|
233
163
|
"""通过XML直接设置文字颜色,确保不被覆盖"""
|
|
234
164
|
rPr = run._r.get_or_add_rPr()
|
|
235
|
-
|
|
236
|
-
existing = rPr.find(qn(tag))
|
|
237
|
-
if existing is not None:
|
|
238
|
-
rPr.remove(existing)
|
|
165
|
+
_clear_fill_elements(rPr)
|
|
239
166
|
solidFill = OxmlElement('a:solidFill')
|
|
240
167
|
srgbClr = OxmlElement('a:srgbClr')
|
|
241
168
|
srgbClr.set('val', hex_color.lstrip('#'))
|
|
@@ -289,7 +216,7 @@ def add_text_box(slide, text, x, y, width, height, font_size, color, bold=False,
|
|
|
289
216
|
|
|
290
217
|
return tb
|
|
291
218
|
|
|
292
|
-
def add_rectangle(slide, x, y, width, height, fill_color, opacity=1.0, rx=0, stroke_color=None, stroke_width=0, stroke_opacity=None):
|
|
219
|
+
def add_rectangle(slide, x, y, width, height, fill_color, opacity=1.0, rx=0, stroke_color=None, stroke_width=0, stroke_opacity=None, has_shadow=False):
|
|
293
220
|
"""添加矩形"""
|
|
294
221
|
shape = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE if rx > 0 else MSO_SHAPE.RECTANGLE,
|
|
295
222
|
px(x), px(y), px(width), px(height))
|
|
@@ -304,15 +231,17 @@ def add_rectangle(slide, x, y, width, height, fill_color, opacity=1.0, rx=0, str
|
|
|
304
231
|
if stroke_color and stroke_width > 0:
|
|
305
232
|
shape.line.color.rgb = hex_to_rgb(stroke_color)
|
|
306
233
|
shape.line.width = int(stroke_width * 9525)
|
|
307
|
-
# 设置边框透明度
|
|
308
234
|
if stroke_opacity is not None and stroke_opacity < 1.0:
|
|
309
235
|
set_line_stroke_transparency(shape, stroke_opacity)
|
|
310
236
|
else:
|
|
311
237
|
shape.line.fill.background()
|
|
312
238
|
|
|
239
|
+
if not has_shadow:
|
|
240
|
+
remove_shadow(shape)
|
|
241
|
+
|
|
313
242
|
return shape
|
|
314
243
|
|
|
315
|
-
def add_gradient_rectangle(slide, x, y, width, height, color1, alpha1, color2, alpha2, angle=0, rx=0):
|
|
244
|
+
def add_gradient_rectangle(slide, x, y, width, height, color1, alpha1, color2, alpha2, angle=0, rx=0, has_shadow=False):
|
|
316
245
|
"""添加渐变矩形"""
|
|
317
246
|
shape = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE if rx > 0 else MSO_SHAPE.RECTANGLE,
|
|
318
247
|
px(x), px(y), px(width), px(height))
|
|
@@ -321,18 +250,27 @@ def add_gradient_rectangle(slide, x, y, width, height, color1, alpha1, color2, a
|
|
|
321
250
|
|
|
322
251
|
set_shape_gradient_fill(shape, color1, alpha1, color2, alpha2, angle)
|
|
323
252
|
shape.line.fill.background()
|
|
253
|
+
|
|
254
|
+
if not has_shadow:
|
|
255
|
+
remove_shadow(shape)
|
|
256
|
+
|
|
324
257
|
return shape
|
|
325
258
|
|
|
326
|
-
def
|
|
327
|
-
"""
|
|
328
|
-
|
|
259
|
+
def remove_shadow(shape):
|
|
260
|
+
"""移除形状阴影效果"""
|
|
261
|
+
if hasattr(shape, '_sp'):
|
|
262
|
+
spPr = shape._sp.spPr
|
|
263
|
+
else:
|
|
264
|
+
spPr = shape._element.find(qn('p:spPr'))
|
|
265
|
+
if spPr is None:
|
|
266
|
+
return
|
|
329
267
|
effectLst = spPr.find(qn('a:effectLst'))
|
|
330
268
|
if effectLst is not None:
|
|
331
269
|
spPr.remove(effectLst)
|
|
332
270
|
effectLst = OxmlElement('a:effectLst')
|
|
333
271
|
spPr.append(effectLst)
|
|
334
272
|
|
|
335
|
-
def add_circle(slide, cx, cy, r, fill_color=None, stroke_color=None, stroke_width=1, opacity=1.0, stroke_opacity=None):
|
|
273
|
+
def add_circle(slide, cx, cy, r, fill_color=None, stroke_color=None, stroke_width=1, opacity=1.0, stroke_opacity=None, has_shadow=False):
|
|
336
274
|
"""添加圆形"""
|
|
337
275
|
shape = slide.shapes.add_shape(MSO_SHAPE.OVAL, px(cx-r), px(cy-r), px(r*2), px(r*2))
|
|
338
276
|
|
|
@@ -352,13 +290,12 @@ def add_circle(slide, cx, cy, r, fill_color=None, stroke_color=None, stroke_widt
|
|
|
352
290
|
else:
|
|
353
291
|
shape.line.fill.background()
|
|
354
292
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
remove_autoshape_shadow(shape)
|
|
293
|
+
if not has_shadow:
|
|
294
|
+
remove_shadow(shape)
|
|
358
295
|
|
|
359
296
|
return shape
|
|
360
297
|
|
|
361
|
-
def add_ellipse(slide, cx, cy, rx, ry, fill_color=None, stroke_color=None, stroke_width=1, opacity=1.0, rotation=0, stroke_opacity=None):
|
|
298
|
+
def add_ellipse(slide, cx, cy, rx, ry, fill_color=None, stroke_color=None, stroke_width=1, opacity=1.0, rotation=0, stroke_opacity=None, has_shadow=False):
|
|
362
299
|
"""添加椭圆"""
|
|
363
300
|
shape = slide.shapes.add_shape(MSO_SHAPE.OVAL, px(cx-rx), px(cy-ry), px(rx*2), px(ry*2))
|
|
364
301
|
|
|
@@ -373,7 +310,6 @@ def add_ellipse(slide, cx, cy, rx, ry, fill_color=None, stroke_color=None, strok
|
|
|
373
310
|
if stroke_color:
|
|
374
311
|
shape.line.color.rgb = hex_to_rgb(stroke_color)
|
|
375
312
|
shape.line.width = int(stroke_width * 9525)
|
|
376
|
-
# 设置边框透明度
|
|
377
313
|
if stroke_opacity is not None and stroke_opacity < 1.0:
|
|
378
314
|
set_line_stroke_transparency(shape, stroke_opacity)
|
|
379
315
|
else:
|
|
@@ -382,18 +318,10 @@ def add_ellipse(slide, cx, cy, rx, ry, fill_color=None, stroke_color=None, strok
|
|
|
382
318
|
if rotation != 0:
|
|
383
319
|
shape.rotation = rotation
|
|
384
320
|
|
|
385
|
-
|
|
321
|
+
if not has_shadow:
|
|
322
|
+
remove_shadow(shape)
|
|
386
323
|
|
|
387
|
-
|
|
388
|
-
"""移除形状阴影效果"""
|
|
389
|
-
spPr = shape._element.find(qn('p:spPr'))
|
|
390
|
-
if spPr is None:
|
|
391
|
-
return
|
|
392
|
-
effectLst = spPr.find(qn('a:effectLst'))
|
|
393
|
-
if effectLst is not None:
|
|
394
|
-
spPr.remove(effectLst)
|
|
395
|
-
effectLst = OxmlElement('a:effectLst')
|
|
396
|
-
spPr.append(effectLst)
|
|
324
|
+
return shape
|
|
397
325
|
|
|
398
326
|
def add_line(slide, x1, y1, x2, y2, color, width=1, opacity=1.0, dash=False):
|
|
399
327
|
"""添加线条"""
|
|
@@ -404,10 +332,10 @@ def add_line(slide, x1, y1, x2, y2, color, width=1, opacity=1.0, dash=False):
|
|
|
404
332
|
connector.line.dash_style = 2
|
|
405
333
|
if opacity < 1.0:
|
|
406
334
|
set_line_transparency(connector, opacity)
|
|
407
|
-
|
|
335
|
+
remove_shadow(connector)
|
|
408
336
|
return connector
|
|
409
337
|
|
|
410
|
-
def add_triangle(slide, cx, cy, size, fill_color, opacity=1.0, rotation=0, stroke_color=None, stroke_width=0, stroke_opacity=None):
|
|
338
|
+
def add_triangle(slide, cx, cy, size, fill_color, opacity=1.0, rotation=0, stroke_color=None, stroke_width=0, stroke_opacity=None, has_shadow=False):
|
|
411
339
|
"""添加三角形"""
|
|
412
340
|
shape = slide.shapes.add_shape(MSO_SHAPE.ISOSCELES_TRIANGLE,
|
|
413
341
|
px(cx - size/2), px(cy - size/2), px(size), px(size))
|
|
@@ -420,11 +348,14 @@ def add_triangle(slide, cx, cy, size, fill_color, opacity=1.0, rotation=0, strok
|
|
|
420
348
|
if stroke_color and stroke_width > 0:
|
|
421
349
|
shape.line.color.rgb = hex_to_rgb(stroke_color)
|
|
422
350
|
shape.line.width = int(stroke_width * 9525)
|
|
423
|
-
# 设置边框透明度
|
|
424
351
|
if stroke_opacity is not None and stroke_opacity < 1.0:
|
|
425
352
|
set_line_stroke_transparency(shape, stroke_opacity)
|
|
426
353
|
else:
|
|
427
354
|
shape.line.fill.background()
|
|
355
|
+
|
|
356
|
+
if not has_shadow:
|
|
357
|
+
remove_shadow(shape)
|
|
358
|
+
|
|
428
359
|
return shape
|
|
429
360
|
|
|
430
361
|
def add_spline(slide, points, color, width=2, opacity=1.0, smooth=True):
|
|
@@ -448,16 +379,7 @@ def add_spline(slide, points, color, width=2, opacity=1.0, smooth=True):
|
|
|
448
379
|
set_line_transparency(shape, opacity)
|
|
449
380
|
return shape
|
|
450
381
|
|
|
451
|
-
def
|
|
452
|
-
"""移除自由形状阴影效果"""
|
|
453
|
-
spPr = shape._sp.spPr
|
|
454
|
-
effectLst = spPr.find(qn('a:effectLst'))
|
|
455
|
-
if effectLst is not None:
|
|
456
|
-
spPr.remove(effectLst)
|
|
457
|
-
effectLst = OxmlElement('a:effectLst')
|
|
458
|
-
spPr.append(effectLst)
|
|
459
|
-
|
|
460
|
-
def add_freeform_path(slide, points, fill_color=None, stroke_color=None, stroke_width=1, opacity=1.0, closed=False, stroke_opacity=None):
|
|
382
|
+
def add_freeform_path(slide, points, fill_color=None, stroke_color=None, stroke_width=1, opacity=1.0, closed=False, stroke_opacity=None, has_shadow=False):
|
|
461
383
|
"""添加自由形状路径"""
|
|
462
384
|
if len(points) < 2:
|
|
463
385
|
return None
|
|
@@ -485,9 +407,8 @@ def add_freeform_path(slide, points, fill_color=None, stroke_color=None, stroke_
|
|
|
485
407
|
else:
|
|
486
408
|
shape.line.fill.background()
|
|
487
409
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
remove_freeform_shadow(shape)
|
|
410
|
+
if not has_shadow:
|
|
411
|
+
remove_shadow(shape)
|
|
491
412
|
|
|
492
413
|
return shape
|
|
493
414
|
|
|
@@ -593,6 +514,106 @@ def add_circular_image(slide, image_path, cx, cy, r):
|
|
|
593
514
|
shape = slide.shapes.add_picture(buffer, px(cx - r), px(cy - r), px(diameter), px(diameter))
|
|
594
515
|
return shape
|
|
595
516
|
|
|
517
|
+
def add_pie_chart(slide, cx, cy, r, data, colors, labels=None, start_angle=-90, stroke_color=None, stroke_width=0, opacity=1.0, has_shadow=False):
|
|
518
|
+
total = sum(data)
|
|
519
|
+
if total == 0:
|
|
520
|
+
return []
|
|
521
|
+
|
|
522
|
+
shapes = []
|
|
523
|
+
current_angle = start_angle
|
|
524
|
+
|
|
525
|
+
for i, value in enumerate(data):
|
|
526
|
+
if value <= 0:
|
|
527
|
+
continue
|
|
528
|
+
|
|
529
|
+
percentage = value / total
|
|
530
|
+
sweep_angle = percentage * 360
|
|
531
|
+
|
|
532
|
+
color = colors[i % len(colors)]
|
|
533
|
+
shape = _add_pie_slice(slide, cx, cy, r, current_angle, sweep_angle,
|
|
534
|
+
color, stroke_color, stroke_width, opacity, has_shadow)
|
|
535
|
+
if shape:
|
|
536
|
+
shapes.append(shape)
|
|
537
|
+
|
|
538
|
+
current_angle += sweep_angle
|
|
539
|
+
|
|
540
|
+
return shapes
|
|
541
|
+
|
|
542
|
+
def _add_pie_slice(slide, cx, cy, r, start_angle, sweep_angle, fill_color, stroke_color=None, stroke_width=0, opacity=1.0, has_shadow=False):
|
|
543
|
+
if sweep_angle >= 360:
|
|
544
|
+
sweep_angle = 359.99
|
|
545
|
+
|
|
546
|
+
start_rad = math.radians(start_angle)
|
|
547
|
+
end_rad = math.radians(start_angle + sweep_angle)
|
|
548
|
+
|
|
549
|
+
points = [(cx, cy)]
|
|
550
|
+
|
|
551
|
+
num_segments = max(int(sweep_angle / 5), 2)
|
|
552
|
+
for i in range(num_segments + 1):
|
|
553
|
+
angle = start_rad + (end_rad - start_rad) * i / num_segments
|
|
554
|
+
x = cx + r * math.cos(angle)
|
|
555
|
+
y = cy + r * math.sin(angle)
|
|
556
|
+
points.append((x, y))
|
|
557
|
+
|
|
558
|
+
points.append((cx, cy))
|
|
559
|
+
|
|
560
|
+
shape = add_freeform_path(slide, points, fill_color=fill_color,
|
|
561
|
+
stroke_color=stroke_color, stroke_width=stroke_width,
|
|
562
|
+
opacity=opacity, closed=True, has_shadow=has_shadow)
|
|
563
|
+
return shape
|
|
564
|
+
|
|
565
|
+
def add_donut_chart(slide, cx, cy, outer_r, inner_r, data, colors, labels=None, start_angle=-90, stroke_color=None, stroke_width=0, opacity=1.0, has_shadow=False):
|
|
566
|
+
total = sum(data)
|
|
567
|
+
if total == 0:
|
|
568
|
+
return []
|
|
569
|
+
|
|
570
|
+
shapes = []
|
|
571
|
+
current_angle = start_angle
|
|
572
|
+
|
|
573
|
+
for i, value in enumerate(data):
|
|
574
|
+
if value <= 0:
|
|
575
|
+
continue
|
|
576
|
+
|
|
577
|
+
percentage = value / total
|
|
578
|
+
sweep_angle = percentage * 360
|
|
579
|
+
|
|
580
|
+
color = colors[i % len(colors)]
|
|
581
|
+
shape = _add_donut_slice(slide, cx, cy, outer_r, inner_r, current_angle, sweep_angle,
|
|
582
|
+
color, stroke_color, stroke_width, opacity, has_shadow)
|
|
583
|
+
if shape:
|
|
584
|
+
shapes.append(shape)
|
|
585
|
+
|
|
586
|
+
current_angle += sweep_angle
|
|
587
|
+
|
|
588
|
+
return shapes
|
|
589
|
+
|
|
590
|
+
def _add_donut_slice(slide, cx, cy, outer_r, inner_r, start_angle, sweep_angle, fill_color, stroke_color=None, stroke_width=0, opacity=1.0, has_shadow=False):
|
|
591
|
+
if sweep_angle >= 360:
|
|
592
|
+
sweep_angle = 359.99
|
|
593
|
+
|
|
594
|
+
start_rad = math.radians(start_angle)
|
|
595
|
+
end_rad = math.radians(start_angle + sweep_angle)
|
|
596
|
+
|
|
597
|
+
points = []
|
|
598
|
+
|
|
599
|
+
num_segments = max(int(sweep_angle / 5), 2)
|
|
600
|
+
for i in range(num_segments + 1):
|
|
601
|
+
angle = start_rad + (end_rad - start_rad) * i / num_segments
|
|
602
|
+
x = cx + outer_r * math.cos(angle)
|
|
603
|
+
y = cy + outer_r * math.sin(angle)
|
|
604
|
+
points.append((x, y))
|
|
605
|
+
|
|
606
|
+
for i in range(num_segments, -1, -1):
|
|
607
|
+
angle = start_rad + (end_rad - start_rad) * i / num_segments
|
|
608
|
+
x = cx + inner_r * math.cos(angle)
|
|
609
|
+
y = cy + inner_r * math.sin(angle)
|
|
610
|
+
points.append((x, y))
|
|
611
|
+
|
|
612
|
+
shape = add_freeform_path(slide, points, fill_color=fill_color,
|
|
613
|
+
stroke_color=stroke_color, stroke_width=stroke_width,
|
|
614
|
+
opacity=opacity, closed=True, has_shadow=has_shadow)
|
|
615
|
+
return shape
|
|
616
|
+
|
|
596
617
|
def create_slide(prs):
|
|
597
618
|
"""深度学习的崛起(1990-2017)"""
|
|
598
619
|
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
|
@@ -751,6 +772,25 @@ def create_slide(prs):
|
|
|
751
772
|
# SVG: <text x="320" y="650" font-size="32" fill="#ffffff">🧠</text>
|
|
752
773
|
add_text_box(slide, "🧠", 320, 650, 50, 40, 32, "#ffffff", anchor='start', valign='top')
|
|
753
774
|
|
|
775
|
+
# 饼状图演示:展示各技术占比
|
|
776
|
+
# SVG: <g transform="translate(500, 550)">
|
|
777
|
+
# <!-- 饼状图: CNN 40%, RNN 25%, GAN 20%, Transformer 15% -->
|
|
778
|
+
# </g>
|
|
779
|
+
pie_data = [40, 25, 20, 15]
|
|
780
|
+
pie_colors = ["#ff6384", "#36a2eb", "#ffce56", "#4bc0c0"]
|
|
781
|
+
add_pie_chart(slide, 500, 580, 50, pie_data, pie_colors, stroke_color="#1a237e", stroke_width=1)
|
|
782
|
+
add_text_box(slide, "技术分布", 500, 520, 100, 20, 12, "#b3e5fc", anchor='middle', valign='top')
|
|
783
|
+
|
|
784
|
+
# 环形图演示:展示GPU使用率
|
|
785
|
+
# SVG: <g transform="translate(600, 550)">
|
|
786
|
+
# <!-- 环形图: 训练 60%, 推理 30%, 空闲 10% -->
|
|
787
|
+
# </g>
|
|
788
|
+
donut_data = [60, 30, 10]
|
|
789
|
+
donut_colors = ["#00e5ff", "#2979ff", "#ffffff"]
|
|
790
|
+
add_donut_chart(slide, 600, 580, 50, 30, donut_data, donut_colors, stroke_color="#1a237e", stroke_width=1, opacity=0.9)
|
|
791
|
+
add_text_box(slide, "GPU", 600, 580, 40, 20, 14, "#ffffff", bold=True, anchor='middle', valign='middle')
|
|
792
|
+
add_text_box(slide, "使用率", 600, 520, 100, 20, 12, "#b3e5fc", anchor='middle', valign='top')
|
|
793
|
+
|
|
754
794
|
def main():
|
|
755
795
|
prs = Presentation()
|
|
756
796
|
prs.slide_width = px(1280)
|
|
@@ -763,4 +803,4 @@ def main():
|
|
|
763
803
|
print(f"成功生成: {output_path}")
|
|
764
804
|
|
|
765
805
|
if __name__ == "__main__":
|
|
766
|
-
main()
|
|
806
|
+
main()
|
package/index.js
CHANGED
|
@@ -122,7 +122,7 @@ const SNIPPETS = {
|
|
|
122
122
|
'【渐变透明度】每个渐变停止点的透明度需在对应 srgbClr 下追加 a:alpha 元素,val 值为透明度*100000(如 0.5 透明度对应 50000)。',
|
|
123
123
|
'【曲线绘制】使用 build_freeform 绘制曲线时,起点坐标在构造器中指定,后续点通过 add_line_segments 添加。若需平滑曲线,可用 Catmull-Rom 样条插值生成中间点。',
|
|
124
124
|
'【线宽单位换算】线条宽度不能使用 Pt,应使用 EMU 单位:width * 9525(1px 约等于 9525 EMU)。',
|
|
125
|
-
'
|
|
125
|
+
'【移除阴影效果】如果 SVG 代码中没有显式设置阴影(如 box-shadow 或 filter: drop-shadow),插入元素后需要移除默认阴影,方法是在 spPr 下添加空的 a:effectLst 元素。',
|
|
126
126
|
'【Emoji 处理】Emoji 可以像普通文本一样直接插入到 PPT 中,使用 add_text_box 函数即可,无需特殊处理。'
|
|
127
127
|
]
|
|
128
128
|
}
|
|
@@ -284,7 +284,7 @@ const listTools = () => ({
|
|
|
284
284
|
]
|
|
285
285
|
});
|
|
286
286
|
|
|
287
|
-
const server = new Server({ name: 'skills', version: '3.0.
|
|
287
|
+
const server = new Server({ name: 'skills', version: '3.0.3' }, { capabilities: { tools: {} } });
|
|
288
288
|
|
|
289
289
|
server.setRequestHandler(ListToolsRequestSchema, async () => listTools());
|
|
290
290
|
|
package/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@ww_nero/skills",
|
|
3
|
-
"version": "3.0.
|
|
4
|
-
"description": "MCP server that returns Python reference snippets and skill guides",
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"bin": {
|
|
7
|
-
"skills": "index.js"
|
|
8
|
-
},
|
|
9
|
-
"files": [
|
|
10
|
-
"index.js",
|
|
11
|
-
"assets"
|
|
12
|
-
],
|
|
13
|
-
"dependencies": {
|
|
14
|
-
"@modelcontextprotocol/sdk": "^1.22.0"
|
|
15
|
-
}
|
|
16
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@ww_nero/skills",
|
|
3
|
+
"version": "3.0.3",
|
|
4
|
+
"description": "MCP server that returns Python reference snippets and skill guides",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"skills": "index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"index.js",
|
|
11
|
+
"assets"
|
|
12
|
+
],
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@modelcontextprotocol/sdk": "^1.22.0"
|
|
15
|
+
}
|
|
16
|
+
}
|