def coordinate(self, idx): """ Return coordinate of cell area ordered starting from top_left going clockwise. :param idx: 0,1,2,3 and 4 for CENTER :return: """ if not isinstance(idx, int): raise TypeError row_idx, col_idx = self._parent.cell_idx(self) tbl = self._tc.getparent().getparent() # table is inside graphic frame. Graphic frame is a thing that's inside slide. gf = tbl.getparent().getparent().getparent() top_left = [gf.x, gf.y] for tr in tbl.tr_lst[:row_idx]: top_left[1] += tr.h for gc in tbl.tblGrid.gridCol_lst[:col_idx]: top_left[0] += gc.w if idx == 0: return Emu(top_left[0]), Emu(top_left[1]) elif idx == 1: return Emu(top_left[0] + self.width), Emu(top_left[1]) elif idx == 2: return Emu(top_left[0]) + self.width, Emu(top_left[1] + self.height) elif idx == 3: return Emu(top_left[0]), Emu(top_left[1] + self.height) elif idx == 4: return Emu(top_left[0] + self.width / 2), Emu(top_left[1] + self.height / 2) else: raise ValueError
def when_I_call_shapes_add_movie(context): shapes = context.shapes x, y, cx, cy = Emu(2590800), Emu(571500), Emu(3962400), Emu(5715000) context.movie = shapes.add_movie( test_file('just-two-mice.mp4'), x, y, cx, cy, test_file('just-two-mice.png') )
def _get_marX(self, attr_name, default): """ Generalized method to get margin values. """ if self.tcPr is None: return Emu(default) return Emu(int(self.tcPr.get(attr_name, default)))
def when_I_assign_value_to_paragraph_line_spacing(context, value_str): value = { '1.5': 1.5, '2.0': 2.0, '254000': Emu(254000), '304800': Emu(304800), 'None': None, }[value_str] paragraph = context.paragraph paragraph.line_spacing = value
def _cy(self): """Emu object specifying height of "show-as-icon" image for OLE shape.""" # --- a user-specified width overrides any default --- if self._cy_arg is not None: return self._cy_arg # --- the default height is specified by the PROG_ID member if prog_id is one, # --- otherwise it gets the default icon height. return (Emu(self._prog_id_arg.height) if self._prog_id_arg in PROG_ID else Emu(609600))
def when_I_assign_value_to_paragraph_line_spacing(context, value_str): value = { "1.5": 1.5, "2.0": 2.0, "254000": Emu(254000), "304800": Emu(304800), "None": None, }[value_str] paragraph = context.paragraph paragraph.line_spacing = value
def _cx(self): """Emu object specifying width of "show-as-icon" image for OLE shape.""" # --- a user-specified width overrides any default --- if self._cx_arg is not None: return self._cx_arg # --- the default width is specified by the PROG_ID member if prog_id is one, # --- otherwise it gets the default icon width. return (Emu(self._prog_id_arg.width) if self._prog_id_arg in PROG_ID else Emu(965200))
def add_slide_to_ppt(ppt, image): """ append an slide to the presentation with the image """ size = (Emu(ppt.slide_width).pt, Emu(ppt.slide_height).pt) blank_slide_layout = ppt.slide_layouts[6] left = top = Emu(0) slide = ppt.slides.add_slide(blank_slide_layout) image = resize_patch_image(image, size) logging.info("adding image to presentation, slides: [{}]".format( len(ppt.slides))) pic = slide.shapes.add_picture(image, left, top)
def cy(self): """ Shape height as an instance of Emu, or None if not present. """ cy_str_lst = self.xpath("./a:xfrm/a:ext/@cy") if not cy_str_lst: return None return Emu(cy_str_lst[0])
def cx(self): """ Shape width as an instance of Emu, or None if not present. """ cx_str_lst = self.xpath("./a:xfrm/a:ext/@cx") if not cx_str_lst: return None return Emu(cx_str_lst[0])
def add_image_slide(self, image_downloaded): image_layout = self.presentation.slide_layouts[SLD_LAYOUT_IMAGE] slide = self.presentation.slides.add_slide(image_layout) picture = self.picture_fitted(slide, image_downloaded) pos_x = (self.presentation.slide_width.inches - Emu(picture.width).inches) / 2 picture.left = Inches(pos_x).emu
def convert_from_xml(cls, str_value): float_part, units_part = str_value[:-2], str_value[-2:] quantity = float(float_part) multiplier = { 'mm': 36000, 'cm': 360000, 'in': 914400, 'pt': 12700, 'pc': 152400, 'pi': 152400 }[units_part] emu_value = Emu(int(round(quantity * multiplier))) return emu_value
def begin_x(self): """ Return the X-position of the begin point of this connector, in English Metric Units (as a |Length| object). """ cxnSp = self._element x, cx, flipH = cxnSp.x, cxnSp.cx, cxnSp.flipH begin_x = x + cx if flipH else x return Emu(begin_x)
def y(self): """ The offset of the top of the shape from the top of the slide, as an instance of Emu. None if not present. """ y_str_lst = self.xpath("./a:xfrm/a:off/@y") if not y_str_lst: return None return Emu(y_str_lst[0])
def begin_y(self): """ Return the Y-position of the begin point of this connector, in English Metric Units (as a |Length| object). """ cxnSp = self._element y, cy, flipV = cxnSp.y, cxnSp.cy, cxnSp.flipV begin_y = y + cy if flipV else y return Emu(begin_y)
class CT_TextBodyProperties(BaseOxmlElement): """ <a:bodyPr> custom element class """ eg_textAutoFit = ZeroOrOneChoice( (Choice("a:noAutofit"), Choice("a:normAutofit"), Choice("a:spAutoFit")), successors=("a:scene3d", "a:sp3d", "a:flatTx", "a:extLst"), ) lIns = OptionalAttribute("lIns", ST_Coordinate32, default=Emu(91440)) tIns = OptionalAttribute("tIns", ST_Coordinate32, default=Emu(45720)) rIns = OptionalAttribute("rIns", ST_Coordinate32, default=Emu(91440)) bIns = OptionalAttribute("bIns", ST_Coordinate32, default=Emu(45720)) anchor = OptionalAttribute("anchor", MSO_VERTICAL_ANCHOR) wrap = OptionalAttribute("wrap", ST_TextWrappingType) @property def autofit(self): """ The autofit setting for the text frame, a member of the ``MSO_AUTO_SIZE`` enumeration. """ if self.noAutofit is not None: return MSO_AUTO_SIZE.NONE if self.normAutofit is not None: return MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE if self.spAutoFit is not None: return MSO_AUTO_SIZE.SHAPE_TO_FIT_TEXT return None @autofit.setter def autofit(self, value): if value is not None and value not in MSO_AUTO_SIZE._valid_settings: raise ValueError( "only None or a member of the MSO_AUTO_SIZE enumeration can " "be assigned to CT_TextBodyProperties.autofit, got %s" % value) self._remove_eg_textAutoFit() if value == MSO_AUTO_SIZE.NONE: self._add_noAutofit() elif value == MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE: self._add_normAutofit() elif value == MSO_AUTO_SIZE.SHAPE_TO_FIT_TEXT: self._add_spAutoFit()
def x(self): """ The offset of the left edge of the shape from the left edge of the slide, as an instance of Emu. Corresponds to the value of the `./xfrm/off/@x` attribute. None if not present. """ x_str_lst = self.xpath("./a:xfrm/a:off/@x") if not x_str_lst: return None return Emu(x_str_lst[0])
def _new_placeholder_table(self, rows, cols): """ Return a newly added `p:graphicFrame` element containing an empty table with *rows* rows and *cols* columns, positioned at the location of this placeholder and having its same width. The table's height is determined by the number of rows. """ shape_id, name, height = self.shape_id, self.name, Emu(rows * 370840) return CT_GraphicalObjectFrame.new_table_graphicFrame( shape_id, name, rows, cols, self.left, self.top, self.width, height)
class CT_LineProperties(BaseOxmlElement): """Custom element class for <a:ln> element""" _tag_seq = ( "a:noFill", "a:solidFill", "a:gradFill", "a:pattFill", "a:prstDash", "a:custDash", "a:round", "a:bevel", "a:miter", "a:headEnd", "a:tailEnd", "a:extLst", ) eg_lineFillProperties = ZeroOrOneChoice( ( Choice("a:noFill"), Choice("a:solidFill"), Choice("a:gradFill"), Choice("a:pattFill"), ), successors=_tag_seq[4:], ) prstDash = ZeroOrOne("a:prstDash", successors=_tag_seq[5:]) custDash = ZeroOrOne("a:custDash", successors=_tag_seq[6:]) del _tag_seq w = OptionalAttribute("w", ST_LineWidth, default=Emu(0)) @property def eg_fillProperties(self): """ Required to fulfill the interface used by dml.fill. """ return self.eg_lineFillProperties @property def prstDash_val(self): """Return value of `val` attribute of `a:prstDash` child. Return |None| if not present. """ prstDash = self.prstDash if prstDash is None: return None return prstDash.val @prstDash_val.setter def prstDash_val(self, val): self._remove_custDash() prstDash = self.get_or_add_prstDash() prstDash.val = val
def _child_extents(self): """(x, y, cx, cy) tuple representing net position and size. The values are formed as a composite of the contained child shapes. """ child_shape_elms = list(self.iter_shape_elms()) if not child_shape_elms: return Emu(0), Emu(0), Emu(0), Emu(0) min_x = min([xSp.x for xSp in child_shape_elms]) min_y = min([xSp.y for xSp in child_shape_elms]) max_x = max([(xSp.x + xSp.cx) for xSp in child_shape_elms]) max_y = max([(xSp.y + xSp.cy) for xSp in child_shape_elms]) x = min_x y = min_y cx = max_x - min_x cy = max_y - min_y return x, y, cx, cy
def orient(self, x, y, ref_idx=0): """ Change position of shape by placing vertex[ref_idx] to position x,y :param x: destination x :param y: destination y :param ref_idx: reference vertex to move with :return: """ if not isinstance(ref_idx, int): raise TypeError if not isinstance(x, Length) or not isinstance(y, Length): raise TypeError if ref_idx == 0: self._element.x = x self._element.y = y elif ref_idx == 1: self._element.x = Emu(x - self._element.cx) elif ref_idx == 2: self._element.x = Emu(x - self._element.cx) self._element.y = Emu(y - self._element.cy) elif ref_idx == 3: self._element.y = Emu(y - self._element.cy) elif ref_idx == 4: self._element.x = Emu(x - self._element.cx / 2) self._element.y = Emu(y - self._element.cy / 2) else: raise ValueError
def convert_from_xml(cls, str_value): float_part, units_part = str_value[:-2], str_value[-2:] quantity = float(float_part) multiplier = { "mm": 36000, "cm": 360000, "in": 914400, "pt": 12700, "pc": 152400, "pi": 152400, }[units_part] emu_value = Emu(int(round(quantity * multiplier))) return emu_value
def coordinate(self, idx): if not isinstance(idx, int): raise TypeError top_left = [self._element.x, self._element.y] w, h = self._element.cx, self._element.cy if idx == 0: return tuple(top_left) elif idx == 1: return Emu(top_left[0] + w), Emu(top_left[1]) elif idx == 2: return Emu(top_left[0] + w), Emu(top_left[1] + h) elif idx == 3: return Emu(top_left[0]), Emu(top_left[1] + h) elif idx == 4: return Emu(top_left[0] + w / 2), Emu(top_left[1] + h / 2) else: raise ValueError
def convert_to_xml(cls, value): length = Emu(value) # just to make sure return str(length.centipoints)
def convert_from_xml(cls, str_value): return Emu(str_value)
def convert_from_xml(cls, str_value): int_value = super(ST_PositiveCoordinate, cls).convert_from_xml(str_value) return Emu(int_value)
def convert_from_xml(cls, str_value): if "i" in str_value or "m" in str_value or "p" in str_value: return ST_UniversalMeasure.convert_from_xml(str_value) return Emu(int(str_value))
def plot_chart(prs,df,chart_type,title=u'我是标题',summary=u'我是简短的结论',\ footnote=None,chart_format=None,layouts=[0,0],has_data_labels=True): ''' 直接将数据绘制到一张ppt上,且高度定制化 默认都有图例,且图例在下方 默认都有数据标签 ''' slide_width = prs.slide_width slide_height = prs.slide_height # 可能需要修改以适应更多的情形 # layouts[0]代表第几个母版,layouts[1]代表母版中的第几个版式 title_only_slide = prs.slide_masters[layouts[0]].slide_layouts[layouts[1]] slide = prs.slides.add_slide(title_only_slide) # 添加标题 title=u'这里是标题' try: slide.shapes.title.text = title except: print('请检查模板,脚本没有找到合适的slide') return # 添加结论 summary=u'这里是一些简短的结论' #summary_loc=[0.10,0.14,0.80,0.15] left, top = Emu(config.summary_loc[0] * slide_width), Emu( config.summary_loc[1] * slide_height) width, height = Emu(config.summary_loc[2] * slide_width), Emu( config.summary_loc[3] * slide_height) txBox = slide.shapes.add_textbox(left, top, width, height) txBox.text_frame.text = summary txBox.text_frame.paragraphs[0].font.language_id = 3076 try: txBox.text_frame.fit_text(max_size=12) except: pass #print('cannot fit the size of font') # 添加脚注 footnote=u'这里是脚注' if footnote: left, top = Emu(0.025 * slide_width), Emu(0.95 * slide_height) width, height = Emu(0.70 * slide_width), Emu(0.10 * slide_height) txBox = slide.shapes.add_textbox(left, top, width, height) #p = text_frame.paragraphs[0] p = txBox.text_frame.paragraphs[0] p.text = footnote p.font.size = Pt(10) p.font.language_id = 3076 p.font.name = 'Microsoft YaHei UI' p.font.color.rgb = RGBColor(127, 127, 127) try: txBox.text_frame.fit_text(max_size=10) except: pass #print('cannot fit the size of font') # 插入图表 chart_type_code = chart_list[chart_type][1] chart_data = df_to_chartdata(df, chart_type_code) #left, top = Emu(0.05*slide_width), Emu(0.20*slide_height) #width, height = Emu(0.85*slide_width), Emu(0.70*slide_height) #chart_loc=[0.10,0.30,0.80,0.60] left, top = Emu(config.chart_loc[0] * slide_width), Emu( config.chart_loc[1] * slide_height) width, height = Emu(config.chart_loc[2] * slide_width), Emu( config.chart_loc[3] * slide_height) chart=slide.shapes.add_chart(chart_list[chart_type.upper()][0], \ left, top, width, height, chart_data).chart if chart_type_code in [-4169, 72, 73, 74, 75]: return font_default_size = Pt(10) # 添加图例 if (df.shape[1] > 1) or (chart_type == 'PIE'): chart.has_legend = True chart.legend.font.size = font_default_size chart.legend.position = XL_LEGEND_POSITION.BOTTOM chart.legend.include_in_layout = False try: chart.category_axis.tick_labels.font.size = font_default_size except: pass #暂时不知道怎么处理 try: chart.value_axis.tick_labels.font.size = font_default_size except: pass # 添加数据标签 non_available_list=['BUBBLE','BUBBLE_THREE_D_EFFECT','XY_SCATTER',\ 'XY_SCATTER_LINES','PIE'] # 大致检测是否采用百分比 # 1、单选题每列的和肯定是100,顶多相差+-5 # 2、多选题每一列的和大于100,但单个的小于100.此处可能会有误判,但暂时无解 # 3、可能会有某一列全为0,此时单独考虑 if ((df.sum()[df.sum() != 0] > 90).all()) and ( (df <= 100).all().all()) and (u'总体' not in df.index): # 数据条的数据标签格式 #number_format1='0.0"%"' number_format1 = config.number_format_data # 坐标轴的数据标签格式 #number_format2='0"%"' number_format2 = config.number_format_tick else: number_format1 = '0.00' number_format2 = '0.0' if (chart_type not in non_available_list) or (chart_type == 'PIE'): plot = chart.plots[0] plot.has_data_labels = True plot.data_labels.font.size = font_default_size plot.data_labels.number_format = number_format1 #plot.data_labels.number_format_is_linked=True #data_labels = plot.data_labels #plot.data_labels.position = XL_LABEL_POSITION.BEST_FIT if (chart_type not in non_available_list): #chart.value_axis.maximum_scale = 1 if df.shape[1] == 1: chart.value_axis.has_major_gridlines = False else: chart.value_axis.has_major_gridlines = True tick_labels = chart.value_axis.tick_labels tick_labels.number_format = number_format2 tick_labels.font.size = font_default_size # 修改纵坐标格式 ''' tick_labels = chart.value_axis.tick_labels tick_labels.number_format = '0"%"' tick_labels.font.bold = True tick_labels.font.size = Pt(10) ''' # 填充系列的颜色 ''' 最好的方法还是修改母版文件中的主题颜色,这里只提供方法 if df.shape[1]==1: chart.series[0].fill() ''' # 自定义format if chart_format: for k in chart_format: exec('chart.' + k + '=' + '%s' % (chart_format[k])) return prs '''
def plot_cover(prs, title=u'reportgen工具包封面', layouts=[0, 0], xspace=8, yspace=6): slide_width = prs.slide_width slide_height = prs.slide_height # 可能需要修改以适应更多的情形 title_only_slide = prs.slide_masters[layouts[0]].slide_layouts[layouts[1]] slide = prs.slides.add_slide(title_only_slide) ## 随机生成连接点 seeds = np.round( np.dot(np.random.rand((xspace - 1) * (yspace - 1), 2), np.diag([slide_width, slide_height]))) # 添加左边点 tmp = np.linspace(0, slide_height, yspace) seeds = np.concatenate((seeds, np.array([[0] * len(tmp), tmp]).T)) # 添加上边点 tmp = np.linspace(0, slide_width, xspace)[1:] seeds = np.concatenate((seeds, np.array([tmp, [0] * len(tmp)]).T)) # 添加右边点 tmp = np.linspace(0, slide_height, yspace)[1:] seeds = np.concatenate((seeds, np.array([[slide_width] * len(tmp), tmp]).T)) # 添加下边点 tmp = np.linspace(0, slide_width, xspace)[1:-1] seeds = np.concatenate((seeds, np.array([tmp, [slide_height] * len(tmp)]).T)) # 构造三角剖分,生成相应的三角形和平面图数据 center = np.mean(seeds, axis=0) t = np.sqrt(slide_width**2 + slide_height**2) / 2 dt = Delaunay2D(center, 2**(np.floor(np.log2(t)) + 1)) for s in seeds: dt.AddPoint(s) tri = dt.exportTriangles() graph = np.zeros((len(seeds), len(seeds))) for t in tri: graph[t[0], t[1]] = 1 graph[t[1], t[2]] = 1 graph[t[0], t[2]] = 1 graph[t[1], t[0]] = 1 graph[t[2], t[1]] = 1 graph[t[2], t[1]] = 1 from pptx.enum.shapes import MSO_CONNECTOR from pptx.enum.shapes import MSO_SHAPE shapes = slide.shapes # 添加连接线 for i in range(len(seeds)): for j in range(len(seeds)): if (i < j) and graph[i, j] == 1: shapes.add_connector(MSO_CONNECTOR.STRAIGHT, Emu(seeds[i, 0]), Emu(seeds[i, 1]), Emu(seeds[j, 0]), Emu(seeds[j, 1])) # 添加圆点,原点的半径符合高斯分布 radius = slide_width / 100 for i in range(len(seeds)): eps = np.random.normal(scale=radius * 0.2) left = Emu(seeds[i, 0]) - radius - eps top = Emu(seeds[i, 1]) - radius - eps width = height = 2 * (radius + eps) shape = shapes.add_shape(MSO_SHAPE.OVAL, left, top, width, height) shape.line.width = Emu(0) fill = shape.fill fill.solid() fill.fore_color.rgb = RGBColor(218, 227, 243) # 添加标题 left, top = Emu(0), Emu(0.4 * slide_height) width, height = Emu(1 * slide_width), Emu(0.2 * slide_height) shape = shapes.add_shape(MSO_SHAPE.RECTANGLE, left, top, width, height) shape.line.width = Emu(0) fill = shape.fill fill.solid() fill.fore_color.rgb = RGBColor(0, 176, 240) shape.text = title # 添加脚注 left, top = Emu(0.72 * slide_width), Emu(0.93 * slide_height) width, height = Emu(0.25 * slide_width), Emu(0.07 * slide_height) txBox = slide.shapes.add_textbox(left, top, width, height) txBox.text_frame.text = 'POWERED BY REPORTGEN' # 添加LOGO logo_path = os.path.join(_thisdir, 'images', 'logo.png') if os.path.exists(logo_path): left, top = Emu(0.65 * slide_width), Emu(0.94 * slide_height) height = Emu(0.06 * slide_height) slide.shapes.add_picture(logo_path, left, top, height=height) return prs
def location_suggest(self, num=1, rate=0.78, data=None, summary=None): '''统一管理slides各个模块的位置 parameter -------- num: 主体内容(如图、外链图片、文本框等)的个数,默认从左到右依次排列 rate: 主体内容的宽度综合 data: list,通过数据类型智能判断位置,如有,则 num 失效 summary:如果summary为空,则非图表等位置都会上移动 return ----- locations: dict格式. l代表left,t代表top,w代表width,h代表height ''' slide_width, slide_height = self.prs.slide_width, self.prs.slide_height if 'summary_loc' in config.__dict__: summary_loc = config.summary_loc else: summary_loc = [0.10, 0.14, 0.80, 0.15] if 'footnote_loc' in config.__dict__: footnote_loc = config.footnote_loc else: footnote_loc = [0.025, 0.95, 0.70, 0.06] if 'data_loc' in config.__dict__: data_loc = config.data_loc else: data_loc = [0.11, 0.30, 0.78, 0.60] num = len(data) if isinstance(data, list) else num locations = {} locations['summary']={'l':Emu(summary_loc[0]*slide_width),'t':Emu(summary_loc[1]*slide_height),\ 'w':Emu(summary_loc[2]*slide_width),'h':Emu(summary_loc[3]*slide_height)} locations['footnote']={'l':Emu(footnote_loc[0]*slide_width),'t':Emu(footnote_loc[1]*slide_height),\ 'w':Emu(footnote_loc[2]*slide_width),'h':Emu(footnote_loc[3]*slide_height)} # 主体部分只有一个的情形 ''' 控制主体的宽度为78%,且居中显示。 ''' if (summary is not None) and len(summary) == 0: data_loc[1] = data_loc[1] * 0.84 if num > 1: left = [ (1 - rate) * (i + 1) / (float(num) + 1) + rate * i / float(num) for i in range(num) ] top = [data_loc[1]] * num width = [rate / float(num)] * num height = [data_loc[3]] * num locations['data']=[{'l':Emu(left[i]*slide_width),'t':Emu(top[i]*slide_height),\ 'w':Emu(width[i]*slide_width),'h':Emu(height[i]*slide_height)} for i in range(num)] else: # 暂时只修正单张图片常常不居中的问题,后期会修正多张图片 if data[0]['slide_type'] == 'picture': imgdata = mpimg.imread(data[0]['data']) img_height, img_width = imgdata.shape[:2] img_width_in_pptx = data_loc[ 3] * slide_height * img_width / img_height / slide_width data_loc[0] = 0.5 - img_width_in_pptx / 2 locations['data']=[{'l':Emu(data_loc[0]*slide_width),'t':Emu(data_loc[1]*slide_height),\ 'w':Emu(data_loc[2]*slide_width),'h':Emu(data_loc[3]*slide_height)}] return locations