import io from pptx import Presentation from pptx.enum.shapes import MSO_SHAPE_TYPE from pptx.enum.text import PP_ALIGN from pptx.shapes.group import GroupShape from PIL import Image, ImageDraw, ImageFont import skia def transfer_textbox_content_in_group(group_shape): """Edit the content of text boxes within a group shape.""" group_shape_item = {} for l, shape in enumerate(group_shape.shapes): shape_item = {} if shape.has_text_frame: shape_item['type'] = "text" shape_item['location'] = (shape.left, shape.top) text_frame = shape.text_frame for r, paragraph in enumerate(text_frame.paragraphs): original_run = paragraph.runs[0] paragraph_item = {} paragraph_item['text'] = paragraph.text paragraph_item['align'] = paragraph.alignment font_item = {} font_item['name'] = original_run.font.name font_item['size'] = original_run.font.size font_item['bold'] = original_run.font.bold font_item['italic'] = original_run.font.italic font_item['underline'] = original_run.font.underline font_item['color'] = original_run.font.color.rgb font_item['language_id'] = original_run.font.language_id paragraph_item['font'] = font_item shape_item[f'paragraph_{r}'] = paragraph_item group_shape_item[f"shape_{l}"] = shape_item return group_shape_item def pptx_to_images(pptx_path): # Load the PowerPoint presentation prs = Presentation(pptx_path) # List to hold the images images = [] # Conversion factor from EMUs to pixels EMU_TO_PIXELS = 914400 / 96 # Conversion factor from EMUs to points EMU_TO_POINTS = 12700 # Load a default font default_font = ImageFont.load_default() # Iterate through each slide in the presentation for slide in prs.slides: # Convert slide dimensions from EMUs to pixels slide_width = int(prs.slide_width / EMU_TO_PIXELS) slide_height = int(prs.slide_height / EMU_TO_PIXELS) # Create a blank image with the same dimensions as the slide img = Image.new('RGB', (slide_width, slide_height), color='white') draw = ImageDraw.Draw(img) # Draw the slide content onto the image for shape in slide.shapes: if shape.has_text_frame: # Convert shape position from EMUs to pixels shape_left = int(shape.left / EMU_TO_PIXELS) shape_top = int(shape.top / EMU_TO_PIXELS) text_frame = shape.text_frame for paragraph in text_frame.paragraphs: original_run = paragraph.runs[0] font = ImageFont.truetype(original_run.font.name, original_run.font.size) draw.text((shape_left, shape_top), paragraph.text, fill=original_run.font.color.rgb, font=font) elif isinstance(shape, GroupShape): group_content = transfer_textbox_content_in_group(shape) for shape_key, shape_item in group_content.items(): if shape_item['type'] == "text": text_location = shape_item['location'] text_left = int(text_location[0] / EMU_TO_PIXELS) text_top = int(text_location[1] / EMU_TO_PIXELS) for paragraph_key, paragraph_item in shape_item.items(): if paragraph_key.startswith('paragraph_'): font_info = paragraph_item['font'] # font = ImageFont.load_default() print(font_info['size']) font_size = int(font_info['size'] / EMU_TO_POINTS) # Convert EMUs to points font = ImageFont.truetype("fonts/FXZK-FANXMLT.ttf", font_size) # font = ImageFont.truetype(font_info['name'], font_info['size']) # Calculate text bounding box bbox = draw.textbbox((0, 0), paragraph_item['text'], font=font) text_width = bbox[2] - bbox[0] text_height = bbox[3] - bbox[1] # Adjust text position based on alignment if paragraph_item['align'] == PP_ALIGN.LEFT: text_left = text_left elif paragraph_item['align'] == PP_ALIGN.CENTER: text_left = text_left + (shape.width / EMU_TO_PIXELS - text_width) / 2 elif paragraph_item['align'] == PP_ALIGN.RIGHT: text_left = text_left + shape.width / EMU_TO_PIXELS - text_width else: text_left = text_left draw.text((text_left, text_top), paragraph_item['text'], fill=font_info['color'], font=font) elif shape.shape_type == MSO_SHAPE_TYPE.PICTURE: # Convert shape position and size from EMUs to pixels picture_left = int(shape.left / EMU_TO_PIXELS) picture_top = int(shape.top / EMU_TO_PIXELS) picture_width = int(shape.width / EMU_TO_PIXELS) picture_height = int(shape.height / EMU_TO_PIXELS) # Load the picture and resize it to the correct dimensions picture = shape.image.blob picture_img = Image.open(io.BytesIO(picture)) picture_img = picture_img.resize((picture_width, picture_height), Image.Resampling.LANCZOS) # Paste the picture onto the image img.paste(picture_img, (picture_left, picture_top)) # Convert the PIL image to a byte stream img_byte_arr = io.BytesIO() img.save(img_byte_arr, format='PNG') img_byte_arr.seek(0) # Append the image byte stream to the list images.append(img_byte_arr) return images, (slide_width, slide_height) def render_images_with_skia(images, slide_dimensions, output_dir): # Create the output directory if it doesn't exist import os if not os.path.exists(output_dir): os.makedirs(output_dir) # Create a Skia surface slide_width, slide_height = slide_dimensions surface = skia.Surface(slide_width, slide_height) with surface as canvas: for i, img_byte_arr in enumerate(images): # Load the image into a Skia image skia_image = skia.Image.MakeFromEncoded(img_byte_arr.getvalue()) # Draw the image onto the canvas canvas.drawImage(skia_image, 0, 0) # Save the canvas to a file output_path = os.path.join(output_dir, f'slide_{i+1}.png') surface.makeImageSnapshot().save(output_path, skia.kPNG) return output_path if __name__ == "__main__": pptx_path = 'templates_from_gaoding/ppt/1.pptx' # Replace with your .pptx file path output_dir = 'output_slides' # Directory to save the rendered images images, slide_dimensions = pptx_to_images(pptx_path) output_path = render_images_with_skia(images, slide_dimensions, output_dir)