mirrored 11 minutes ago
0
Adam Yanxiao ZhaoAdd AutoGLM-OS agent (#309) * autoglm-os initialize * clean code * chore: use proxy for download setup * feat(autoglm-os): add parameter to toggle images * fix: use temporary directory for files pulled from the vm to prevent potential collision when running multiple instances of the same task in parallel * update * add client_password * update multienv * fix * fix prompt * fix prompt * fix prompt * fix sys prompt * feat: use proxy in file evaluator * fix client_password * fix note_prompt * fix autoglm agent cmd type * fix * revert: fix: use temporary directory for files pulled from the vm to prevent potential collision when running multiple instances of the same task in parallel reverts commit bab5473eea1de0e61b0e1d68b23ce324a5b0ee57 * feat(autoglm): setup tools * fix(autoglm): remove second time of get a11y tree * add osworld server restart * Revert "add osworld server restart" This reverts commit 7bd9d84122e246ce2a26de0e49c25494244c2b3d. * fix _launch_setup * fix autoglm agent tools & xml tree * fix desktop_env * fix bug for tool name capitalization * fix: always use proxy for setup download * add fail after exceeding max turns * fix(autoglm): avoid adding image to message when screenshot is empty * fix maximize_window * fix maximize_window * fix maximize_window * fix import browsertools module bug * fix task proxy config bug * restore setup * refactor desktop env * restore image in provider * restore file.py * refactor desktop_env * quick fix * refactor desktop_env.step * fix our env reset * add max truns constraint * clean run script * clean lib_run_single.py --------- Co-authored-by: hanyullai <hanyullai@outlook.com> Co-authored-by: JingBh <jingbohao@yeah.net>aa05f6c
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