@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.
@@ -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 = OxmlElement('a: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
- clr = elm.srgbClr
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
- if srgbClr is not None:
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
- for tag in ['a:solidFill', 'a:gradFill', 'a:noFill']:
124
- existing = spPr.find(qn(tag))
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
- if srgbClr is not None:
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
- if srgbClr is not None:
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
- for tag in ['a:solidFill', 'a:gradFill', 'a:noFill']:
201
- existing = rPr.find(qn(tag))
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
- for tag in ['a:solidFill', 'a:gradFill', 'a:noFill']:
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 remove_autoshape_shadow(shape):
327
- """移除自动形状阴影效果"""
328
- spPr = shape._sp.spPr
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
- # 如果只有stroke没有fill,移除阴影
356
- if not fill_color and stroke_color:
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
- return shape
321
+ if not has_shadow:
322
+ remove_shadow(shape)
386
323
 
387
- def remove_shape_shadow(shape):
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
- remove_shape_shadow(connector)
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 remove_freeform_shadow(shape):
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
- if not closed:
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
- '【移除阴影效果】对于线条、非封闭的 freeform path、以及只有 stroke 没有 fill circle,需要移除默认阴影效果,方法是在 spPr 下添加空的 a:effectLst 元素。',
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.1' }, { capabilities: { tools: {} } });
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.1",
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
+ }