Chapter 12. Examining a Draw/Impress Document

This chapter describes the Slides Info example or more specifically slide_info.py, which shows the basics of how to open and display a Draw or Impress file. The code also illustrates how the slides or pages can be examined, including how to retrieve information on their layers and styles.

The slide_info.py main function:

class SlidesInfo:
    def __init__(self, fnm: PathOrStr) -> None:
        FileIO.is_exist_file(fnm=fnm, raise_err=True)
        self._fnm = FileIO.get_absolute_path(fnm)

    def main(self) -> None:
        loader = Lo.load_office(Lo.ConnectPipe())

        try:
            doc = Lo.open_doc(fnm=self._fnm, loader=loader)

            if not Draw.is_shapes_based(doc):
                Lo.print("-- not a drawing or slides presentation")
                Lo.close_doc(doc)
                Lo.close_office()
                return

            GUI.set_visible(is_visible=True, odoc=doc)
            Lo.delay(1_000)  # need delay or zoom nay not occur

            GUI.zoom(view=GUI.ZoomEnum.ENTIRE_PAGE)

            print()
            print(f"No. of slides: {Draw.get_slides_count(doc)}")
            print()

            # Access the first page
            slide = Draw.get_slide(doc=doc, idx=0)

            slide_size = Draw.get_slide_size(slide)
            print(f"Size of slide (mm)({slide_size.Width}, {slide_size.Height})")
            print()

            self._report_layers(doc)
            self._report_styles(doc)

            Lo.delay(1_000)
            msg_result = MsgBox.msgbox(
                "Do you wish to close document?",
                "All done",
                boxtype=MessageBoxType.QUERYBOX,
                buttons=MessageBoxButtonsEnum.BUTTONS_YES_NO,
            )
            if msg_result == MessageBoxResultsEnum.YES:
                Lo.close_doc(doc=doc, deliver_ownership=True)
                Lo.close_office()
            else:
                print("Keeping document open")
        except Exception:
            Lo.close_office()
            raise

Lo.open_doc() is capable of opening any Office document, and importing documents in other formats, so it’s worthwhile checking the resulting XComponent object before progressing. Draw.is_shapes_based() returns true if the file holds a Draw or Impress document:

@staticmethod
def is_shapes_based(doc: XComponent) -> bool:
    return Info.is_doc_type(obj=doc, doc_type=mLo.Lo.Service.DRAW) or Info.is_doc_type(
        obj=doc, doc_type=mLo.Lo.Service.IMPRESS
    )

The document is made visible on-screen by calling GUI.set_visible(), and the application view is resized so all the drawing (or slide) is visible inside the window. GUI.zoom() can be passed different GUI.ZoomEnum values for showing, ZoomEnum.PAGE_WIDTH, the entire width of the page, ZoomEnum.ENTIRE_PAGE, the entire page, ZoomEnum.OPTIMAL. an optimal view that zooms in so all the ‘data’ on the page is visible without the empty space around it. Alternatively, ZoomEnum.BY_VALUE with an integer value allows the user to supply a zooming percentage. eg: Draw.zoom(view=GUI.ZoomEnum.BY_VALUE, value=75)

These two methods are defined using Lo.dispatch_cmd(), which was introduced at the end of Chapter 4. Listening, and Other Techniques.

The call to Lo.delay() at the end of the zoom methods gives Office time to carry out the zooming before my code does anything else. The same trick is utilized in the main() method, after the call to GUI.set_visible().

12.1 Accessing Slides/Pages

Most Draw class method names include the word ‘slide’ eg: ( Draw.get_slides_count(), Draw.get_slide(), Draw.get_slide_size() ). That’s a bit misleading since most of them will work just as well with Draw or Impress documents. For example, Draw.get_slides_count() will return 1 when applied to a newly created Draw document.

Draw.get_slides_count() calls Draw.get_slides() which returns an XDrawPages object; which supports a getCount() method:

# in the Draw class (simplified)
@classmethod
def get_slides_count(cls, doc: XComponent) -> int:
    slides = cls.get_slides(doc)
    if slides is None:
        return 0
    return slides.getCount()

@staticmethod
def get_slides(doc: XComponent) -> XDrawPages:
    try:
        supplier = Lo.qi(XDrawPagesSupplier, doc, True)
        pages = supplier.getDrawPages()
        if pages is None:
            raise DrawPageMissingError("Draw page supplier returned no pages")
        return pages
    except DrawPageMissingError:
        raise
    except Exception as e:
        raise DrawPageError("Error getting slides") from e

get_slides() employs the XDrawPagesSupplier interface which is part of GenericDrawingDocument shown in Fig. 91.

Draw.get_slide() (note: no “s”) treats the XDrawPages object as an indexed container of XDrawPage objects:

# from draw class (simplified)
@classmethod
def get_slide(cls, doc: XComponent, idx: int) -> XDrawPage:
    # call: get_slide(cls, slides: XDrawPages, idx: int)
    return cls.get_slide(cls.get_slides(doc), idx)

@classmethod
def get_slide(cls, slides: XDrawPages, idx: int) -> XDrawPage:
    try:
        slide = Lo.qi(XDrawPage, slides.getByIndex(idx), True)
        return slide
    except IndexOutOfBoundsException:
        raise IndexError(f"Index out of bounds: {idx}")
    except Exception as e:
        raise DrawError(f"Could not get slide: {idx}") from e

Draw.get_slide_size() returns a com.sun.star.awt.Size object created from looking up the Width and Height properties of the supplied slide/page:

# from Draw class (simplified)
@staticmethod
def get_slide_size(slide: XDrawPage) -> Size:
    try:
        props = Lo.qi(XPropertySet, slide)
        if props is None:
            raise PropertySetMissingError("No slide properties found")
        width = int(props.getPropertyValue("Width"))
        height = int(props.getPropertyValue("Height"))
        return Size(round(width / 100), round(height / 100))
    except Exception as e:
        raise SizeError("Could not get shape size") from e

These Width and Height properties are stored in XDrawPage’s GenericDrawPage service, shown in Fig. 95.

Important

The Draw class specifies measurements in millimeters rather than Office’s 1/100 mm units. For instance, Draw.get_slide_size() would return Office page dimensions of 28000 by 21000 as (280, 210).

12.2 Page Layers

A Draw or Impress page consists of five layers called layout, controls, measurelines, background, and backgroundobjects. The first three are described in the Draw user guide, but measurelines is called “Dimension Lines”.

Probably layout is the most important layer since that’s where shapes are located. Form controls (e.g. buttons) are added to “controls”, which is always the top-most layer. background, and backgroundobjects refer to the master page graphic and any shapes on that page.

Each layer can be made visible or invisible independent of the others. It’s also possible to create new layers.

_report_layers() in slide_info.py prints each layer’s properties:

# in slide_info.py
def _report_layers(self, doc: XComponent) -> None:
    lm = Draw.get_layer_manager(doc)
    for i in range(lm.getCount()):
        try:
            Props.show_obj_props(f" Layer {i}", lm.getByIndex(i))
        except:
            pass
    layer = Draw.get_layer(doc=doc, layer_name=DrawingLayerKind.BACK_GROUND_OBJECTS)
    Props.show_obj_props("Background Object Props", layer)

Draw.get_layer_manager() obtains an XLayerManager instance which can be treated as an indexed container of XLayer objects. Draw.get_layer() converts the XLayerManager into a named container, so it can be searched by layer name.

Each layer contains six properties. Four are defined by the Layer service; use lodoc layer service drawing to see its documentation. The most useful property is probably IsVisible which toggles the layer’s visibility.

12.3 Styles

Draw and Impress utilize the same style organization as text documents, which was explained in Chapter 6. Text Styles. Fig. 102 shows its structure.

Draw/Impress Style Families and their Property Sets

Fig. 102 :Draw/Impress Style Families and their Property Sets.

The style family names are different from those in text documents. The Default style family corresponds to the styles defined in a document’s default master page.

Fig. 103 shows this master page in Impress.

The Default Master Page in Impress.

Fig. 103 :The Default Master Page in Impress.

The master page (also known as a template in Impress’ GUI) contains style information related to the title, seven outline levels and background areas (e.g. the date, the footer, and the slide number in Fig. 103). Not all the master page styles are shown in Fig. 103; for instance, there’s a subtitle style, notes area, and a header.

If a slide deck is formatted using a master page (Impress template) other than Default, such as Inspiration, then the style family name will be changed accordingly. The Inspiration family contains the same properties (styles) as Default, but with different values.

Details on coding with master pages and Impress templates are given in the master use and points builder examples in Chapter 17. Slide Deck Manipulation.

The other Draw/Impress style families are cell, graphics and table. table and cell contain styles which affect the colors used to draw a table and its cells. graphics affects the appearance of shapes. Examples of how to use the graphics style family are given in the draw picture example in Chapter 14. Animation.

The _report_styles() method inside slide_info.py is:

def _report_styles(self, doc: XComponent) -> None:
    style_names = Info.get_style_family_names(doc)
    print("Style Families in this document:")
    Lo.print_names(style_names)
    # usually: "Default"  "cell"  "graphics"  "table"
    # Default is the name of the default Master Page template inside Office

    for name in style_names:
        con_names = Info.get_style_names(doc=doc, family_style_name=name)
        print(f'Styles in the "{name}" style family:')
        Lo.print_names(con_names)

The method prints the names of the style families, and the names of the styles (property sets) inside each family. Typical output is:

Typical output is:
Style Families in this document:
No. of names: 4
  'cell'  'Default'  'graphics'  'table'

Styles in the "Default" style family:
No. of names: 14
  'background'  'backgroundobjects'  'notes'  'outline1'
  'outline2'  'outline3'  'outline4'  'outline5'
  'outline6'  'outline7'  'outline8'  'outline9'
  'subtitle'  'title'

Styles in the "cell" style family:
No. of names: 34
  'blue1'  'blue2'  'blue3'  'bw1'
  'bw2'  'bw3'  'default'  'earth1'
  'earth2'  'earth3'  'gray1'  'gray2'
  'gray3'  'green1'  'green2'  'green3'
  'lightblue1'  'lightblue2'  'lightblue3'  'orange1'
  'orange2'  'orange3'  'seetang1'  'seetang2'
  'seetang3'  'sun1'  'sun2'  'sun3'
  'turquoise1'  'turquoise2'  'turquoise3'  'yellow1'
  'yellow2'  'yellow3'

Styles in the "graphics" style family:
No. of names: 40
  'A4'  'A4'  'Arrow Dashed'  'Arrow Line'
  'Filled'  'Filled Blue'  'Filled Green'  'Filled Red'
  'Filled Yellow'  'Graphic'  'Heading A0'  'Heading A4'
  'headline'  'headline1'  'headline2'  'Lines'
  'measure'  'Object with no fill and no line'  'objectwitharrow'  'objectwithoutfill'
  'objectwithshadow'  'Outlined'  'Outlined Blue'  'Outlined Green'
  'Outlined Red'  'Outlined Yellow'  'Shapes'  'standard'
  'Text'  'text'  'Text A0'  'Text A4'
  'textbody'  'textbodyindent'  'textbodyjustfied'  'title'
  'Title A0'  'Title A4'  'title1'  'title2'

Styles in the "table" style family:
No. of names: 11
  'blue'  'bw'  'default'  'earth'
  'green'  'lightblue'  'orange'  'seetang'
  'sun'  'turquoise'  'yellow'