import json import os import uno from com.sun.star.awt.FontSlant import ITALIC, NONE from com.sun.star.awt.FontWeight import BOLD, NORMAL from com.sun.star.beans import PropertyValue from com.sun.star.drawing.TextHorizontalAdjust import CENTER, LEFT, RIGHT class ImpressTools: localContext = uno.getComponentContext() resolver = localContext.ServiceManager.createInstanceWithContext("com.sun.star.bridge.UnoUrlResolver", localContext) ctx = resolver.resolve("uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext") desktop = ctx.ServiceManager.createInstanceWithContext("com.sun.star.frame.Desktop", ctx) doc = desktop.getCurrentComponent() ret = "" @classmethod def close_other_window(cls): """关闭除当前文档外的所有文档""" components = cls.desktop.getComponents().createEnumeration() current_url = cls.doc.getURL() while components.hasMoreElements(): doc = components.nextElement() if doc.getURL() != current_url: doc.close(True) @classmethod def save(cls): """保存文档到当前位置""" try: if cls.doc.hasLocation(): cls.doc.store() cls.ret = "Success" else: cls.ret = "Error: Document has no save location" return True except Exception as e: cls.ret = f"Error: {e}" return False @classmethod def maximize_window(cls): """ 将窗口设置为工作区最大尺寸 使用工作区域大小(考虑任务栏等) """ window = cls.doc.getCurrentController().getFrame().getContainerWindow() toolkit = window.getToolkit() device = toolkit.createScreenCompatibleDevice(0, 0) workarea = toolkit.getWorkArea() window.setPosSize(workarea.X, workarea.Y, workarea.Width, workarea.Height, 15) @classmethod def print_result(cls): print(cls.ret) @classmethod def env_info(cls, page_indices=None): """ 获取指定页面的内容 :param page_indices: 页码列表,如果为None则获取所有页面 :return: 包含各页面内容的列表 """ try: pages = cls.doc.getDrawPages() content_str = "" if page_indices is None: page_indices = range(pages.getCount()) for page_index in page_indices: if 0 <= page_index < pages.getCount(): page = pages.getByIndex(page_index) page_content = [] for i in range(page.getCount()): shape = page.getByIndex(i) if hasattr(shape, "getText"): text = shape.getText() if text: page_content.append("- Box " + str(i) + ": " + text.getString().strip()) c = "\n".join(page_content) content_str += f"Slide {page_index+1}:\n{c}\n\n" cur_idx = cls.get_current_slide_index() + 1 content_str = content_str + f"Current Slide Index: {cur_idx}" cls.ret = content_str return content_str except Exception as e: cls.ret = f"Error: {str(e)}" return [] @classmethod def get_current_slide_index(cls): """ Gets the index of the currently active slide in the presentation. :return: The index of the currently active slide (0-based) """ try: controller = cls.doc.getCurrentController() current_page = controller.getCurrentPage() pages = cls.doc.getDrawPages() for i in range(pages.getCount()): if pages.getByIndex(i) == current_page: cls.ret = i return i cls.ret = "Current slide not found" return -1 except Exception as e: cls.ret = f"Error: {str(e)}" return -1 @classmethod def go_to_slide(cls, slide_index): """ Navigates to a specific slide in the presentation based on its index. Args: slide_index (int): The index of the slide to navigate to (1-based indexing) Returns: bool: True if navigation was successful, False otherwise """ try: zero_based_index = slide_index - 1 controller = cls.doc.getCurrentController() if not controller: cls.ret = "Error: Could not get document controller" return False pages = cls.doc.getDrawPages() if zero_based_index < 0 or zero_based_index >= pages.getCount(): cls.ret = f"Error: Slide index {slide_index} is out of range. Valid range is 1-{pages.getCount()}" return False target_slide = pages.getByIndex(zero_based_index) controller.setCurrentPage(target_slide) cls.ret = f"Successfully navigated to slide {slide_index}" return True except Exception as e: cls.ret = f"Error navigating to slide: {str(e)}" return False @classmethod def get_slide_count(cls): """ Gets the total number of slides in the current presentation. :return: The total number of slides as an integer """ try: pages = cls.doc.getDrawPages() count = pages.getCount() cls.ret = count return count except Exception as e: cls.ret = f"Error: {str(e)}" return 0 @classmethod def duplicate_slide(cls, slide_index): """ Creates a duplicate of a specific slide and places it at the end of the presentation. :param slide_index: The index of the slide to duplicate (1-based indexing) :return: True if successful, False otherwise """ try: zero_based_index = slide_index - 1 draw_pages = cls.doc.getDrawPages() if zero_based_index < 0 or zero_based_index >= draw_pages.getCount(): cls.ret = f"Error: Invalid slide index {slide_index}. Valid range is 1 to {draw_pages.getCount()}" return False controller = cls.doc.getCurrentController() controller.setCurrentPage(draw_pages.getByIndex(zero_based_index)) dispatcher = cls.ctx.ServiceManager.createInstanceWithContext("com.sun.star.frame.DispatchHelper", cls.ctx) frame = controller.getFrame() dispatcher.executeDispatch(frame, ".uno:DuplicatePage", "", 0, ()) duplicated_slide_index = zero_based_index + 1 slide_count = draw_pages.getCount() if duplicated_slide_index < slide_count - 1: controller.setCurrentPage(draw_pages.getByIndex(duplicated_slide_index)) moves_needed = slide_count - duplicated_slide_index - 1 for _ in range(moves_needed): dispatcher.executeDispatch(frame, ".uno:MovePageDown", "", 0, ()) cls.ret = f"Slide {slide_index} duplicated successfully and moved to the end" return True except Exception as e: cls.ret = f"Error: {str(e)}" return False @classmethod def set_slide_font(cls, slide_index, font_name): """ Sets the font style for all text elements in a specific slide, including the title. Args: slide_index (int): The index of the slide to modify (1-based indexing) font_name (str): The name of the font to apply (e.g., 'Arial', 'Times New Roman', 'Calibri') Returns: bool: True if successful, False otherwise """ try: zero_based_index = slide_index - 1 slides = cls.doc.getDrawPages() if zero_based_index < 0 or zero_based_index >= slides.getCount(): cls.ret = f"Error: Slide index {slide_index} is out of range. Valid range is 1 to {slides.getCount()}." return False slide = slides.getByIndex(zero_based_index) for i in range(slide.getCount()): shape = slide.getByIndex(i) if hasattr(shape, "getText"): text = shape.getText() if text: cursor = text.createTextCursor() cursor.gotoStart(False) cursor.gotoEnd(True) cursor.setPropertyValue("CharFontName", font_name) cls.ret = f"Successfully set font to '{font_name}' for all text elements in slide {slide_index}." return True except Exception as e: cls.ret = f"Error setting font: {str(e)}" return False @classmethod def write_text(cls, content, page_index, box_index, bold=False, italic=False, size=None, append=False): """ Writes text to a specific textbox on a slide :param content: The text content to add :param page_index: The index of the slide (1-based indexing) :param box_index: The index of the textbox to modify (0-based indexing) :param bold: Whether to make the text bold, default is False :param italic: Whether to make the text italic, default is False :param size: The size of the text. If None, uses the box's current font size. :param append: Whether to append the text, default is False. If you want to observe some formats(like a bullet at the beginning) or keep the original text, you should set up it. :return: True if successful, False otherwise """ try: zero_based_page_index = page_index - 1 pages = cls.doc.getDrawPages() if zero_based_page_index < 0 or zero_based_page_index >= pages.getCount(): cls.ret = f"Error: Page index {page_index} is out of range" return False page = pages.getByIndex(zero_based_page_index) if box_index < 0 or box_index >= page.getCount(): cls.ret = f"Error: Box index {box_index} is out of range" return False shape = page.getByIndex(box_index) if not hasattr(shape, "String"): cls.ret = f"Error: The shape at index {box_index} cannot contain text" return False if append: shape.String = shape.String + content else: shape.String = content if hasattr(shape, "getCharacterProperties"): char_props = shape.getCharacterProperties() if bold: char_props.CharWeight = BOLD else: char_props.CharWeight = NORMAL if italic: char_props.CharPosture = ITALIC else: char_props.CharPosture = NONE if size is not None: char_props.CharHeight = size cls.ret = f"Text successfully written to page {page_index}, box {box_index}" return True except Exception as e: cls.ret = f"Error: {str(e)}" return False @classmethod def set_style(cls, slide_index, box_index, bold=None, italic=None, underline=None): """ Sets the style properties for the specified textbox on a slide. :param slide_index: The index of the slide to modify (1-based indexing) :param box_index: The index of the textbox to modify (0-based indexing) :param bold: Whether to make the text bold :param italic: Whether to make the text italic :param underline: Whether to underline the text :return: True if successful, False otherwise """ try: pages = cls.doc.getDrawPages() if slide_index < 1 or slide_index > pages.getCount(): cls.ret = f"Error: Invalid slide index {slide_index}. Valid range is 1 to {pages.getCount()}" return False page = pages.getByIndex(slide_index - 1) if box_index < 0 or box_index >= page.getCount(): cls.ret = f"Error: Invalid box index {box_index}. Valid range is 0 to {page.getCount() - 1}" return False shape = page.getByIndex(box_index) if not hasattr(shape, "getText"): cls.ret = "Error: The specified shape does not contain text" return False text = shape.getText() cursor = text.createTextCursor() cursor.gotoStart(False) cursor.gotoEnd(True) if bold is not None: cursor.setPropertyValue("CharWeight", BOLD if bold else NORMAL) if italic is not None: cursor.setPropertyValue("CharPosture", ITALIC if italic else NONE) if underline is not None: cursor.setPropertyValue("CharUnderline", 1 if underline else 0) cls.ret = "Style applied successfully" return True except Exception as e: cls.ret = f"Error: {str(e)}" return False @classmethod def configure_auto_save(cls, enabled, interval_minutes): """ Enables or disables auto-save functionality for the current document and sets the auto-save interval. :param enabled: Whether to enable (True) or disable (False) auto-save :param interval_minutes: The interval in minutes between auto-saves (minimum 1 minute) :return: True if successful, False otherwise """ try: if interval_minutes < 1: interval_minutes = 1 config_provider = cls.ctx.ServiceManager.createInstanceWithContext( "com.sun.star.configuration.ConfigurationProvider", cls.ctx ) prop = PropertyValue() prop.Name = "nodepath" prop.Value = "/org.openoffice.Office.Common/Save/Document" config_access = config_provider.createInstanceWithArguments( "com.sun.star.configuration.ConfigurationUpdateAccess", (prop,) ) config_access.setPropertyValue("AutoSave", enabled) config_access.setPropertyValue("AutoSaveTimeIntervall", interval_minutes) config_access.commitChanges() cls.ret = f"Auto-save {'enabled' if enabled else 'disabled'} with interval of {interval_minutes} minutes" return True except Exception as e: cls.ret = f"Error configuring auto-save: {str(e)}" return False @classmethod def set_background_color(cls, slide_index, box_index, color): """ Sets the background color for the specified textbox on a slide. Args: slide_index (int): The index of the slide containing the textbox (1-based indexing) box_index (int): The index of the textbox to modify (0-based indexing) color (str): The color to apply to the textbox (e.g., 'red', 'green', 'blue', 'yellow', or hex color code) Returns: bool: True if successful, False otherwise """ try: zero_based_slide_index = slide_index - 1 slides = cls.doc.getDrawPages() if zero_based_slide_index < 0 or zero_based_slide_index >= slides.getCount(): cls.ret = f"Error: Slide index {slide_index} is out of range" return False slide = slides.getByIndex(zero_based_slide_index) if box_index < 0 or box_index >= slide.getCount(): cls.ret = f"Error: Box index {box_index} is out of range" return False shape = slide.getByIndex(box_index) color_int = 0 color_map = { "red": 16711680, "green": 65280, "blue": 255, "yellow": 16776960, "black": 0, "white": 16777215, "purple": 8388736, "orange": 16753920, "pink": 16761035, "gray": 8421504, "brown": 10824234, "cyan": 65535, "magenta": 16711935, } if color.lower() in color_map: color_int = color_map[color.lower()] elif color.startswith("#") and len(color) == 7: color_int = int(color[1:], 16) else: cls.ret = f"Error: Invalid color format: {color}" return False shape.FillStyle = uno.Enum("com.sun.star.drawing.FillStyle", "SOLID") shape.FillColor = color_int cls.ret = f"Background color of textbox {box_index} on slide {slide_index} set to {color}" return True except Exception as e: cls.ret = f"Error: {str(e)}" return False @classmethod def set_text_color(cls, slide_index, box_index, color): """ Sets the text color for the specified textbox on a slide. Args: slide_index (int): The index of the slide to modify (1-based indexing) box_index (int): The index of the textbox to modify (0-based indexing) color (str): The color to apply to the text (e.g., 'red', 'green', 'blue', 'black', or hex color code) Returns: bool: True if successful, False otherwise """ try: zero_based_slide_index = slide_index - 1 slides = cls.doc.getDrawPages() if zero_based_slide_index < 0 or zero_based_slide_index >= slides.getCount(): cls.ret = f"Error: Slide index {slide_index} is out of range" return False slide = slides.getByIndex(zero_based_slide_index) if box_index < 0 or box_index >= slide.getCount(): cls.ret = f"Error: Box index {box_index} is out of range" return False shape = slide.getByIndex(box_index) if not hasattr(shape, "getText"): cls.ret = f"Error: Shape at index {box_index} does not contain text" return False color_int = 0 if color.startswith("#"): color_int = int(color[1:], 16) else: color_map = { "red": 16711680, "green": 43315, "blue": 255, "black": 0, "white": 16777215, "yellow": 16776960, "cyan": 65535, "magenta": 16711935, "gray": 8421504, } if color.lower() in color_map: color_int = color_map[color.lower()] else: cls.ret = f"Error: Unsupported color '{color}'" return False text = shape.getText() cursor = text.createTextCursor() cursor.gotoStart(False) cursor.gotoEnd(True) cursor.setPropertyValue("CharColor", color_int) cls.ret = f"Successfully set text color to {color} for textbox {box_index} on slide {slide_index}" return True except Exception as e: cls.ret = f"Error: {str(e)}" return False @classmethod def delete_content(cls, slide_index, box_index): """ Deletes the specified textbox from a slide. :param slide_index: The index of the slide to modify (1-based indexing) :param box_index: The index of the textbox to modify (0-based indexing) :return: True if successful, False otherwise """ try: pages = cls.doc.getDrawPages() zero_based_slide_index = slide_index - 1 if zero_based_slide_index < 0 or zero_based_slide_index >= pages.getCount(): cls.ret = f"Error: Invalid slide index {slide_index}. Valid range is 1 to {pages.getCount()}" return False slide = pages.getByIndex(zero_based_slide_index) if box_index < 0 or box_index >= slide.getCount(): cls.ret = f"Error: Invalid box index {box_index}. Valid range is 0 to {slide.getCount() - 1}" return False shape = slide.getByIndex(box_index) slide.remove(shape) cls.ret = f"Successfully deleted textbox {box_index} from slide {slide_index}" return True except Exception as e: cls.ret = f"Error: {str(e)}" return False @classmethod def set_slide_orientation(cls, orientation): """ Changes the orientation of slides in the presentation between portrait (upright) and landscape (sideways). :param orientation: The desired orientation for the slides ('portrait' or 'landscape') :return: True if successful, False otherwise """ try: draw_pages = cls.doc.getDrawPages() first_page = draw_pages.getByIndex(0) current_width = first_page.Width current_height = first_page.Height if orientation == "portrait" and current_width > current_height: new_width, new_height = current_height, current_width elif orientation == "landscape" and current_width < current_height: new_width, new_height = current_height, current_width else: cls.ret = f"Slides are already in {orientation} orientation" return True for i in range(draw_pages.getCount()): page = draw_pages.getByIndex(i) page.Width = new_width page.Height = new_height cls.ret = f"Changed slide orientation to {orientation}" return True except Exception as e: cls.ret = f"Error changing slide orientation: {str(e)}" return False @classmethod def position_box(cls, slide_index, box_index, position): """ Positions a textbox or image on a slide at a specific location or predefined position. :param slide_index: The index of the slide containing the box (1-based indexing) :param box_index: The index of the box to position (0-based indexing) :param position: Predefined position on the slide (left, right, center, top, bottom, etc.) :return: True if successful, False otherwise """ try: pages = cls.doc.getDrawPages() if slide_index < 1 or slide_index > pages.getCount(): cls.ret = f"Error: Invalid slide index {slide_index}" return False page = pages.getByIndex(slide_index - 1) if box_index < 0 or box_index >= page.getCount(): cls.ret = f"Error: Invalid box index {box_index}" return False shape = page.getByIndex(box_index) controller = cls.doc.getCurrentController() slide_width = 28000 slide_height = 21000 shape_width = shape.Size.Width shape_height = shape.Size.Height margin = 500 if position == "left": new_x = margin new_y = (slide_height - shape_height) / 2 elif position == "right": new_x = slide_width - shape_width - margin new_y = (slide_height - shape_height) / 2 elif position == "center": new_x = (slide_width - shape_width) / 2 new_y = (slide_height - shape_height) / 2 elif position == "top": new_x = (slide_width - shape_width) / 2 new_y = margin elif position == "bottom": new_x = (slide_width - shape_width) / 2 new_y = slide_height - shape_height - margin elif position == "top-left": new_x = margin new_y = margin elif position == "top-right": new_x = slide_width - shape_width - margin new_y = margin elif position == "bottom-left": new_x = margin new_y = slide_height - shape_height - margin elif position == "bottom-right": new_x = slide_width - shape_width - margin new_y = slide_height - shape_height - margin else: cls.ret = f"Error: Invalid position '{position}'" return False try: shape.Position.X = int(new_x) shape.Position.Y = int(new_y) except: try: shape.setPropertyValue("PositionX", int(new_x)) shape.setPropertyValue("PositionY", int(new_y)) except: point = uno.createUnoStruct("com.sun.star.awt.Point", int(new_x), int(new_y)) shape.setPosition(point) cls.ret = f"Box positioned at {position} (X: {new_x}, Y: {new_y})" return True except Exception as e: cls.ret = f"Error positioning box: {str(e)}" return False @classmethod def insert_file(cls, file_path, slide_index=None, position=None, size=None, autoplay=False): """ Inserts a video file into the current or specified slide in the presentation. Args: file_path (str): The full path to the video file to be inserted slide_index (int, optional): The index of the slide to insert the video into (1-based indexing). If not provided, inserts into the current slide. position (dict, optional): The position coordinates for the video as percentages of slide dimensions {'x': float, 'y': float} size (dict, optional): The size dimensions for the video as percentages of slide dimensions {'width': float, 'height': float} autoplay (bool, optional): Whether the video should automatically play when the slide is shown Returns: bool: True if successful, False otherwise """ try: expanded_file_path = os.path.expanduser(file_path) if not os.path.exists(expanded_file_path): cls.ret = f"Error: File not found: {expanded_file_path}" return False file_url = uno.systemPathToFileUrl(os.path.abspath(expanded_file_path)) pages = cls.doc.getDrawPages() if slide_index is not None: zero_based_index = slide_index - 1 if zero_based_index < 0 or zero_based_index >= pages.getCount(): cls.ret = f"Error: Invalid slide index: {slide_index}" return False slide = pages.getByIndex(zero_based_index) else: controller = cls.doc.getCurrentController() slide = controller.getCurrentPage() slide_width = 21000 slide_height = 12750 if position is None: position = {"x": 10, "y": 10} if size is None: size = {"width": 80, "height": 60} x = int(position["x"] * slide_width / 100) y = int(position["y"] * slide_height / 100) width = int(size["width"] * slide_width / 100) height = int(size["height"] * slide_height / 100) media_shape = cls.doc.createInstance("com.sun.star.presentation.MediaShape") slide.add(media_shape) media_shape.setPosition(uno.createUnoStruct("com.sun.star.awt.Point", x, y)) media_shape.setSize(uno.createUnoStruct("com.sun.star.awt.Size", width, height)) media_shape.setPropertyValue("MediaURL", file_url) if autoplay: try: media_shape.setPropertyValue("MediaIsAutoPlay", True) except: pass cls.ret = f"Video inserted successfully from {expanded_file_path}" return True except Exception as e: cls.ret = f"Error inserting video: {str(e)}" return False @classmethod def set_slide_background(cls, slide_index=None, color=None, image_path=None): """ Sets the background color or image for a specific slide or all slides. Args: slide_index (int, optional): The index of the slide to modify (1-based indexing). If not provided, applies to all slides. color (str, optional): The background color to apply (e.g., 'red', 'green', 'blue', or hex color code) image_path (str, optional): Path to an image file to use as background. If provided, overrides color. Returns: bool: True if successful, False otherwise """ try: if not color and not image_path: cls.ret = "Error: Either color or image_path must be provided" return False pages = cls.doc.getDrawPages() page_count = pages.getCount() rgb_color = None if color: if color.startswith("#"): color = color.lstrip("#") rgb_color = int(color, 16) else: color_map = { "red": 16711680, "green": 43315, "blue": 255, "black": 0, "white": 16777215, "yellow": 16776960, "cyan": 65535, "magenta": 16711935, "gray": 8421504, } rgb_color = color_map.get(color.lower(), 0) if slide_index is not None: slide_index = slide_index - 1 if slide_index < 0 or slide_index >= page_count: cls.ret = f"Error: Slide index {slide_index + 1} is out of range (1-{page_count})" return False slides_to_modify = [pages.getByIndex(slide_index)] else: slides_to_modify = [pages.getByIndex(i) for i in range(page_count)] for slide in slides_to_modify: fill_props = cls.ctx.ServiceManager.createInstanceWithContext( "com.sun.star.drawing.FillProperties", cls.ctx ) if image_path and os.path.exists(image_path): abs_path = os.path.abspath(image_path) file_url = uno.systemPathToFileUrl(abs_path) fill_props.FillStyle = uno.Enum("com.sun.star.drawing.FillStyle", "BITMAP") fill_props.FillBitmapURL = file_url fill_props.FillBitmapMode = uno.Enum("com.sun.star.drawing.BitmapMode", "STRETCH") elif rgb_color is not None: fill_props.FillStyle = uno.Enum("com.sun.star.drawing.FillStyle", "SOLID") fill_props.FillColor = rgb_color slide.setPropertyValue("Background", fill_props) cls.ret = "Background set successfully" return True except Exception as e: cls.ret = f"Error setting background: {str(e)}" return False @classmethod def save_as(cls, file_path, overwrite=False): """ Saves the current document to a specified location with a given filename. :param file_path: The full path where the file should be saved, including the filename and extension :param overwrite: Whether to overwrite the file if it already exists (default: False) :return: True if successful, False otherwise """ try: if os.path.exists(file_path) and not overwrite: cls.ret = f"File already exists and overwrite is set to False: {file_path}" return False abs_path = os.path.abspath(file_path) if os.name == "nt": url = "file:///" + abs_path.replace("\\", "/") else: url = "file://" + abs_path properties = [] overwrite_prop = PropertyValue() overwrite_prop.Name = "Overwrite" overwrite_prop.Value = overwrite properties.append(overwrite_prop) extension = os.path.splitext(file_path)[1].lower() if extension == ".odp": filter_name = "impress8" elif extension == ".ppt": filter_name = "MS PowerPoint 97" elif extension == ".pptx": filter_name = "Impress MS PowerPoint 2007 XML" elif extension == ".pdf": filter_name = "impress_pdf_Export" else: filter_name = "impress8" filter_prop = PropertyValue() filter_prop.Name = "FilterName" filter_prop.Value = filter_name properties.append(filter_prop) cls.doc.storeAsURL(url, tuple(properties)) cls.ret = f"Document saved successfully to {file_path}" return True except Exception as e: cls.ret = f"Error saving document: {str(e)}" return False @classmethod def insert_image(cls, slide_index, image_path, width=None, height=None, position=None): """ Inserts an image to a specific slide in the presentation. Args: slide_index (int): The index of the slide to add the image to (1-based indexing) image_path (str): The full path to the image file to be added width (float, optional): The width of the image in centimeters height (float, optional): The height of the image in centimeters position (dict, optional): The position coordinates for the image as percentages { 'x': float, # The x-coordinate as a percentage of slide width 'y': float # The y-coordinate as a percentage of slide height } Returns: bool: True if successful, False otherwise """ try: if not os.path.exists(image_path): cls.ret = f"Error: Image file not found at {image_path}" return False zero_based_index = slide_index - 1 slides = cls.doc.getDrawPages() if zero_based_index < 0 or zero_based_index >= slides.getCount(): cls.ret = f"Error: Slide index {slide_index} is out of range. Valid range is 1 to {slides.getCount()}" return False slide = slides.getByIndex(zero_based_index) bitmap = cls.doc.createInstance("com.sun.star.drawing.BitmapTable") image_url = uno.systemPathToFileUrl(os.path.abspath(image_path)) shape = cls.doc.createInstance("com.sun.star.drawing.GraphicObjectShape") shape.setPropertyValue("GraphicURL", image_url) slide.add(shape) x_pos = 0 y_pos = 0 slide_width = slide.Width slide_height = slide.Height if position: if "x" in position: x_pos = int(position["x"] / 100 * slide_width) if "y" in position: y_pos = int(position["y"] / 100 * slide_height) current_width = shape.Size.Width current_height = shape.Size.Height new_width = int(width * 1000) if width is not None else current_width new_height = int(height * 1000) if height is not None else current_height size = uno.createUnoStruct("com.sun.star.awt.Size") size.Width = new_width size.Height = new_height point = uno.createUnoStruct("com.sun.star.awt.Point") point.X = x_pos point.Y = y_pos shape.Size = size shape.Position = point cls.ret = f"Image inserted successfully on slide {slide_index}" return True except Exception as e: cls.ret = f"Error inserting image: {str(e)}" return False @classmethod def configure_display_settings( cls, use_presenter_view=None, primary_monitor_only=None, monitor_for_presentation=None ): """ Configures the display settings for LibreOffice Impress presentations. Args: use_presenter_view (bool, optional): Whether to use presenter view. Set to false to disable presenter view. primary_monitor_only (bool, optional): Whether to use only the primary monitor for the presentation. monitor_for_presentation (int, optional): Specify which monitor to use (1 for primary, 2 for secondary, etc.) Returns: bool: True if settings were successfully applied, False otherwise """ try: controller = cls.doc.getCurrentController() if not hasattr(controller, "getPropertyValue"): cls.ret = "Error: Not an Impress presentation or controller not available" return False if use_presenter_view is not None: try: controller.setPropertyValue("IsPresentationViewEnabled", use_presenter_view) except Exception as e: cls.ret = f"Warning: Could not set presenter view: {str(e)}" if primary_monitor_only is not None: try: controller.setPropertyValue("UsePrimaryMonitorOnly", primary_monitor_only) except Exception as e: cls.ret = f"Warning: Could not set primary monitor usage: {str(e)}" if monitor_for_presentation is not None: try: controller.setPropertyValue("MonitorForPresentation", monitor_for_presentation - 1) except Exception as e: cls.ret = f"Warning: Could not set presentation monitor: {str(e)}" cls.ret = "Display settings configured successfully" return True except Exception as e: cls.ret = f"Error configuring display settings: {str(e)}" return False @classmethod def set_text_strikethrough(cls, slide_index, box_index, line_numbers, apply): """ Applies or removes strike-through formatting to specific text content in a slide. Args: slide_index (int): The index of the slide containing the text (1-based indexing) box_index (int): The index of the textbox containing the text (0-based indexing) line_numbers (list): The line numbers to apply strike-through formatting to (1-based indexing) apply (bool): Whether to apply (true) or remove (false) strike-through formatting Returns: bool: True if successful, False otherwise """ try: slides = cls.doc.getDrawPages() slide = slides.getByIndex(slide_index - 1) shape = slide.getByIndex(box_index) if not hasattr(shape, "getText"): cls.ret = f"Error: Shape at index {box_index} does not contain text" return False text = shape.getText() cursor = text.createTextCursor() text_content = text.getString() lines = text_content.split("\n") for line_number in line_numbers: if 1 <= line_number <= len(lines): start_pos = 0 for i in range(line_number - 1): start_pos += len(lines[i]) + 1 end_pos = start_pos + len(lines[line_number - 1]) cursor.gotoStart(False) cursor.goRight(start_pos, False) cursor.goRight(len(lines[line_number - 1]), True) cursor.CharStrikeout = apply cls.ret = f"Strike-through {'applied' if apply else 'removed'} successfully" return True except Exception as e: cls.ret = f"Error: {str(e)}" return False @classmethod def set_textbox_alignment(cls, slide_index, box_index, alignment): """ Sets the text alignment for the specified textbox on a slide. :param slide_index: The index of the slide to modify (1-based indexing) :param box_index: The index of the textbox to modify (0-based indexing) :param alignment: The text alignment to apply ('left', 'center', 'right', or 'justify') :return: True if successful, False otherwise """ try: zero_based_slide_index = slide_index - 1 slides = cls.doc.getDrawPages() if zero_based_slide_index < 0 or zero_based_slide_index >= slides.getCount(): cls.ret = f"Error: Slide index {slide_index} out of range" return False slide = slides.getByIndex(zero_based_slide_index) if box_index < 0 or box_index >= slide.getCount(): cls.ret = f"Error: Box index {box_index} out of range" return False shape = slide.getByIndex(box_index) if not hasattr(shape, "getText"): cls.ret = "Error: Selected shape does not support text" return False if alignment == "left": shape.TextHorizontalAdjust = LEFT elif alignment == "center": shape.TextHorizontalAdjust = CENTER elif alignment == "right": shape.TextHorizontalAdjust = RIGHT elif alignment == "justify": text = shape.getText() cursor = text.createTextCursor() cursor.gotoStart(False) cursor.gotoEnd(True) cursor.ParaAdjust = 3 else: cls.ret = f"Error: Invalid alignment value: {alignment}" return False cls.ret = f"Successfully set text alignment to {alignment} for textbox {box_index} on slide {slide_index}" return True except Exception as e: cls.ret = f"Error: {str(e)}" return False @classmethod def set_slide_number_properties( cls, color=None, font_size=None, visible=None, position=None, apply_to="all", slide_indices=None ): """ Modifies the properties of slide numbers in the presentation. Args: color (str, optional): The color to apply to slide numbers (e.g., 'red', 'green', 'blue', 'black', or hex color code) font_size (float, optional): The font size for slide numbers (in points) visible (bool, optional): Whether slide numbers should be visible or hidden position (str, optional): The position of slide numbers ('bottom-left', 'bottom-center', 'bottom-right', 'top-left', 'top-center', 'top-right') apply_to (str, optional): Whether to apply changes to 'all', 'current', or 'selected' slides slide_indices (list, optional): Indices of specific slides to change (1-based indexing) Returns: bool: True if successful, False otherwise """ try: draw_pages = cls.doc.getDrawPages() master_pages = cls.doc.getMasterPages() pages_to_modify = [] if apply_to == "all": for i in range(draw_pages.getCount()): pages_to_modify.append(draw_pages.getByIndex(i)) elif apply_to == "current": current_page = cls.doc.getCurrentController().getCurrentPage() pages_to_modify.append(current_page) elif apply_to == "selected" and slide_indices: for idx in slide_indices: if 1 <= idx <= draw_pages.getCount(): pages_to_modify.append(draw_pages.getByIndex(idx - 1)) for i in range(master_pages.getCount()): master_page = master_pages.getByIndex(i) page_number_shape = None for j in range(master_page.getCount()): shape = master_page.getByIndex(j) if hasattr(shape, "TextType"): try: if shape.TextType == 5: page_number_shape = shape break except: pass if hasattr(shape, "getText"): try: text = shape.getText() if text and text.getTextFields().getCount() > 0: fields = text.getTextFields().createEnumeration() while fields.hasMoreElements(): field = fields.nextElement() if "PageNumber" in field.getImplementationName(): page_number_shape = shape break if page_number_shape: break except: pass if page_number_shape: if color is not None: color_int = 0 if color.startswith("#"): color_int = int(color[1:], 16) elif color == "red": color_int = 16711680 elif color == "green": color_int = 65280 elif color == "blue": color_int = 255 elif color == "black": color_int = 0 text = page_number_shape.getText() cursor = text.createTextCursor() cursor.gotoStart(False) cursor.gotoEnd(True) cursor.CharColor = color_int if font_size is not None: text = page_number_shape.getText() cursor = text.createTextCursor() cursor.gotoStart(False) cursor.gotoEnd(True) cursor.CharHeight = font_size if position is not None: page_width = master_page.Width page_height = master_page.Height width = page_number_shape.Size.Width height = page_number_shape.Size.Height new_x = 0 new_y = 0 if position.startswith("bottom"): new_y = page_height - height - 100 elif position.startswith("top"): new_y = 100 if position.endswith("left"): new_x = 100 elif position.endswith("center"): new_x = (page_width - width) / 2 elif position.endswith("right"): new_x = page_width - width - 100 page_number_shape.Position = uno.createUnoStruct("com.sun.star.awt.Point", new_x, new_y) if position.endswith("left"): page_number_shape.ParaAdjust = LEFT elif position.endswith("center"): page_number_shape.ParaAdjust = CENTER elif position.endswith("right"): page_number_shape.ParaAdjust = RIGHT if visible is not None: try: page_number_shape.Visible = visible except: if not visible: page_number_shape.Size = uno.createUnoStruct("com.sun.star.awt.Size", 1, 1) page_number_shape.Position = uno.createUnoStruct("com.sun.star.awt.Point", -1000, -1000) elif ( visible is True or visible is None and (color is not None or font_size is not None or position is not None) ): page_number_shape = cls.doc.createInstance("com.sun.star.drawing.TextShape") master_page.add(page_number_shape) default_width = 2000 default_height = 400 page_number_shape.Size = uno.createUnoStruct("com.sun.star.awt.Size", default_width, default_height) page_width = master_page.Width page_height = master_page.Height pos_x = page_width - default_width - 100 pos_y = page_height - default_height - 100 if position is not None: if position.startswith("bottom"): pos_y = page_height - default_height - 100 elif position.startswith("top"): pos_y = 100 if position.endswith("left"): pos_x = 100 page_number_shape.ParaAdjust = LEFT elif position.endswith("center"): pos_x = (page_width - default_width) / 2 page_number_shape.ParaAdjust = CENTER elif position.endswith("right"): pos_x = page_width - default_width - 100 page_number_shape.ParaAdjust = RIGHT page_number_shape.Position = uno.createUnoStruct("com.sun.star.awt.Point", pos_x, pos_y) text = page_number_shape.getText() cursor = text.createTextCursor() try: page_field = cls.doc.createInstance("com.sun.star.text.TextField.PageNumber") text.insertTextContent(cursor, page_field, False) except: text.setString("<#>") if color is not None: color_int = 0 if color.startswith("#"): color_int = int(color[1:], 16) elif color == "red": color_int = 16711680 elif color == "green": color_int = 65280 elif color == "blue": color_int = 255 elif color == "black": color_int = 0 cursor.gotoStart(False) cursor.gotoEnd(True) cursor.CharColor = color_int if font_size is not None: cursor.gotoStart(False) cursor.gotoEnd(True) cursor.CharHeight = font_size if visible is not None: try: page_number_shape.Visible = visible except: if not visible: page_number_shape.Position = uno.createUnoStruct("com.sun.star.awt.Point", -1000, -1000) else: try: page_number_shape.Visible = True except: pass try: controller = cls.doc.getCurrentController() view_data = controller.getViewData() controller.restoreViewData(view_data) except: pass cls.ret = "Slide number properties updated successfully" return True except Exception as e: cls.ret = f"Error setting slide number properties: {str(e)}" return False @classmethod def set_slide_number(cls, color=None, font_size=None, visible=None, position=None): """ Sets the slide number in the presentation. :param color: The color to apply to slide numbers (e.g., 'red', 'green', 'blue', 'black', or hex color code) :param font_size: The font size for slide numbers (in points) :param visible: Whether slide numbers should be visible or hidden :param position: The position of slide numbers on the slides (bottom-left, bottom-center, bottom-right, top-left, top-center, top-right) :return: True if successful, False otherwise """ try: controller = cls.doc.getCurrentController() dispatcher = cls.ctx.ServiceManager.createInstanceWithContext("com.sun.star.frame.DispatchHelper", cls.ctx) if visible is False: pages = cls.doc.getDrawPages() for i in range(pages.getCount()): page = pages.getByIndex(i) for j in range(page.getCount()): try: shape = page.getByIndex(j) if hasattr(shape, "Presentation") and shape.Presentation == "Number": page.remove(shape) except: pass master_pages = cls.doc.getMasterPages() for i in range(master_pages.getCount()): master_page = master_pages.getByIndex(i) for j in range(master_page.getCount()): try: shape = master_page.getByIndex(j) if hasattr(shape, "Presentation") and shape.Presentation == "Number": master_page.remove(shape) except: pass cls.ret = "Slide numbers hidden successfully" return True if visible is True or color is not None or font_size is not None or position is not None: current_slide = controller.getCurrentPage() master_pages = cls.doc.getMasterPages() if master_pages.getCount() == 0: cls.ret = "No master pages found" return False master_page = master_pages.getByIndex(0) slide_number_shape = cls.doc.createInstance("com.sun.star.drawing.TextShape") slide_number_shape.setSize(uno.createUnoStruct("com.sun.star.awt.Size", 2000, 500)) pos = position or "bottom-right" page_width = master_page.Width page_height = master_page.Height x, y = 0, 0 if "bottom" in pos: y = page_height - 1000 elif "top" in pos: y = 500 if "left" in pos: x = 500 elif "center" in pos: x = (page_width - 2000) / 2 elif "right" in pos: x = page_width - 2500 slide_number_shape.setPosition(uno.createUnoStruct("com.sun.star.awt.Point", x, y)) master_page.add(slide_number_shape) text = slide_number_shape.getText() cursor = text.createTextCursor() page_number = cls.doc.createInstance("com.sun.star.text.TextField.PageNumber") text.insertTextContent(cursor, page_number, False) if "center" in pos: slide_number_shape.setPropertyValue("TextHorizontalAdjust", CENTER) elif "right" in pos: slide_number_shape.setPropertyValue("TextHorizontalAdjust", RIGHT) elif "left" in pos: slide_number_shape.setPropertyValue("TextHorizontalAdjust", LEFT) if font_size is not None: cursor.gotoStart(False) cursor.gotoEnd(True) cursor.setPropertyValue("CharHeight", font_size) if color is not None: cursor.gotoStart(False) cursor.gotoEnd(True) if color.startswith("#") and len(color) == 7: r = int(color[1:3], 16) g = int(color[3:5], 16) b = int(color[5:7], 16) cursor.setPropertyValue("CharColor", (r << 16) + (g << 8) + b) else: color_map = { "red": 16711680, "green": 65280, "blue": 255, "black": 0, "white": 16777215, "yellow": 16776960, "cyan": 65535, "magenta": 16711935, "gray": 8421504, } if color.lower() in color_map: cursor.setPropertyValue("CharColor", color_map[color.lower()]) cls.ret = "Slide numbers added and configured successfully" return True except Exception as e: cls.ret = f"Error setting slide number: {str(e)}" return False @classmethod def set_slide_number_color(cls, color): """ Sets the color of the slide number in the presentation. Args: color (str): The color to apply to slide numbers (e.g., 'red', 'green', 'blue', 'black', or hex color code) Returns: bool: True if successful, False otherwise """ try: color_map = { "black": 0, "white": 16777215, "red": 16711680, "green": 65280, "blue": 255, "yellow": 16776960, "cyan": 65535, "magenta": 16711935, "gray": 8421504, "orange": 16753920, "purple": 8388736, } if color.lower() in color_map: rgb_color = color_map[color.lower()] else: if color.startswith("#"): color = color[1:] try: if len(color) == 6: rgb_color = int(color, 16) else: rgb_color = 0 except ValueError: rgb_color = 0 found = False master_pages = cls.doc.getMasterPages() for i in range(master_pages.getCount()): master_page = master_pages.getByIndex(i) for j in range(master_page.getCount()): shape = master_page.getByIndex(j) if hasattr(shape, "getText") and shape.getText() is not None: text = shape.getText() try: enum = text.createEnumeration() while enum.hasMoreElements(): para = enum.nextElement() if hasattr(para, "createEnumeration"): para_enum = para.createEnumeration() while para_enum.hasMoreElements(): portion = para_enum.nextElement() if ( hasattr(portion, "TextPortionType") and portion.TextPortionType == "TextField" ): if hasattr(portion, "TextField") and portion.TextField is not None: field = portion.TextField if hasattr(field, "supportsService") and ( field.supportsService( "com.sun.star.presentation.TextField.PageNumber" ) or field.supportsService("com.sun.star.text.TextField.PageNumber") ): portion.CharColor = rgb_color found = True except Exception as e: continue draw_pages = cls.doc.getDrawPages() for i in range(draw_pages.getCount()): page = draw_pages.getByIndex(i) for j in range(page.getCount()): shape = page.getByIndex(j) if hasattr(shape, "getText") and shape.getText() is not None: text = shape.getText() try: enum = text.createEnumeration() while enum.hasMoreElements(): para = enum.nextElement() if hasattr(para, "createEnumeration"): para_enum = para.createEnumeration() while para_enum.hasMoreElements(): portion = para_enum.nextElement() if ( hasattr(portion, "TextPortionType") and portion.TextPortionType == "TextField" ): if hasattr(portion, "TextField") and portion.TextField is not None: field = portion.TextField if hasattr(field, "supportsService") and ( field.supportsService( "com.sun.star.presentation.TextField.PageNumber" ) or field.supportsService("com.sun.star.text.TextField.PageNumber") ): portion.CharColor = rgb_color found = True except Exception as e: continue for i in range(draw_pages.getCount()): page = draw_pages.getByIndex(i) for j in range(page.getCount()): shape = page.getByIndex(j) if hasattr(shape, "getText") and shape.getText() is not None: text = shape.getText() text_string = text.getString() if text_string.isdigit() and len(text_string) <= 3: try: cursor = text.createTextCursor() cursor.gotoStart(False) cursor.gotoEnd(True) cursor.CharColor = rgb_color found = True except Exception as e: continue if found: cls.ret = f"Slide number color set to {color}" return True else: cls.ret = "Could not find slide numbers to change color" return False except Exception as e: cls.ret = f"Error setting slide number color: {str(e)}" return False @classmethod def export_to_image(cls, file_path, format, slide_index=None): """ Exports the current presentation or a specific slide to an image file format. Args: file_path (str): The full path where the image file should be saved, including the filename and extension format (str): The image format to export to (e.g., 'png', 'jpeg', 'gif') slide_index (int, optional): The index of the specific slide to export (1-based indexing). If not provided, exports the entire presentation as a series of images. Returns: bool: True if export was successful, False otherwise """ try: format = format.lower() valid_formats = ["png", "jpeg", "jpg", "gif", "bmp", "tiff"] if format not in valid_formats: cls.ret = f"Error: Invalid format '{format}'. Valid formats are: {', '.join(valid_formats)}" return False if format == "jpg": format = "jpeg" pages = cls.doc.getDrawPages() page_count = pages.getCount() if slide_index is not None: slide_index = slide_index - 1 if slide_index < 0 or slide_index >= page_count: cls.ret = f"Error: Invalid slide index {slide_index + 1}. Valid range is 1 to {page_count}" return False controller = cls.doc.getCurrentController() filter_name = f"draw_{format}_Export" filter_data = PropertyValue(Name="FilterData", Value=()) if slide_index is not None: controller.setCurrentPage(pages.getByIndex(slide_index)) props = PropertyValue(Name="FilterName", Value=filter_name), filter_data cls.doc.storeToURL(uno.systemPathToFileUrl(file_path), props) cls.ret = f"Successfully exported slide {slide_index + 1} to {file_path}" return True else: base_name, ext = os.path.splitext(file_path) for i in range(page_count): controller.setCurrentPage(pages.getByIndex(i)) if page_count == 1: current_file = f"{base_name}.{format}" else: current_file = f"{base_name}_{i + 1}.{format}" props = PropertyValue(Name="FilterName", Value=filter_name), filter_data cls.doc.storeToURL(uno.systemPathToFileUrl(current_file), props) if page_count == 1: cls.ret = f"Successfully exported {page_count} slides to {base_name}.{format}" else: cls.ret = f"Successfully exported {page_count} slides to {base_name}_[1-{page_count}].{format}" return True except Exception as e: cls.ret = f"Error exporting to image: {str(e)}" return False