ODFDOM Tutorial Index > Creating Presentation Documents

Creating Presentation Documents Using ODFDOM

The input for the program in this tutorial is a text file with these contents:

// Each line contains:
//   slide title, country name, city name, image path
Rathaus,Germany,Hamburg,images/rathaus.jpg
Namdaemun (남대문),South Korea,Seoul (서을),images/namdaemun.jpg
Louvre Museum,France,Paris,images/louvre.jpg
Opera House,Norway,Oslo,images/oslo_opera_house.jpg

The program’s output will be an OpenDocument presentation file. Here is a slightly edited screenshot of what the first slide will look like.

The area at the left that looks like a table is not an OdfTable like the one in the previous tutorial. Instead, it is a set of four rectangles that have text labels.

The program is written to be run from the command line program; you invoke it with a command like this:

java -cp lib/odfdom-java-0.12.0-jar-with-dependencies.jar:. \
   TravelPresentation slideinfo.txt

The Structure of a Presentation Document

In order for the following code to make sense, you need to know how a presentation document is represented in OpenDocument. An ODF presentation’s content.xml file contains an <office:presentation> element. Each of its <draw:page> children represents one slide. within the document.

Geometric shapes such as rectangles and circles are drawn directly on to the page. Text and images are placed inside a “frame” that contains the position and dimensions of that particular object; in ODFDOM, these are represented by the OdfDrawFrame class.

Variables

The name of the input file and output file will come straight from args[0] and args[1] rather than be stored in a separate variable.

As in the previous tutorials, we do need variables for the presentation document and its “access points.”

class TravelPresentation {
    OdfPresentationDocument outputDocument;
    OfficePresentationElement officePresentation;
    OdfContentDom contentDom;
    OdfOfficeAutomaticStyles contentAutoStyles;

The presentation needs to create automatic styles in the content.xml only, so we will dispense with variables for accessing the styles.xml named and automatic styles.

Once again, because the ODFDOM toolkit automatically generates unique style names for you, the program must keep track of their names in order to apply those styles to elements.

String slideTitleStyleName;
String imageStyleName;
String fauxTableRectangleStyleName;
String fauxTableTextStyleName;

These styles are used for the slide title, the image, the rectangles that make up the simulated (faux) table and the text inside those rectangles.

This having been done, here is the main() method, which creates an application and runs it via the run() method. Notice the call to deleteSlideByIndex(0): when the toolkit creates a presentation document, it has a blank slide at the beginning. We don’t want it, so we’ll delete it right away.

    public static void main(String[] args) {

        TravelPresentation app = new TravelPresentation();
        if (args.length == 2) {
            app.run(args);
        } else {
            System.err.println("Usage: TravelPresentation infile outfile");
        }
    }


    void run(String[] args) {
        setupOutputDocument();
        outputDocument.deleteSlideByIndex(0);
        addAutomaticStyles();
        createPages(args[0]);
        saveOutputDocument(args[1]);
    }

Here’s the code that sets up the output documnet and retrieves the main office:presentation element, content DOM, and the automatic styles in the content.

   private void setupOutputDocument() {
        try {
            outputDocument =
                OdfPresentationDocument.newPresentationDocument();
            officePresentation = outputDocument.getContentRoot();

            contentDom = outputDocument.getContentDom();
            contentAutoStyles = contentDom.getOrCreateAutomaticStyles();
        }
        catch (Exception e) {
            System.err.println("Unable to create output file.");
            System.err.println(e.getMessage());
            outputDocument = null;
        }
    }

Processing the Input Document

This function parses the input file. It does not use any calls to ODFDOM, but it is given here for the sake of completeness. If the parsing goes well, the slideList is filled; otherwise it is set to null.

private void processInput(String inputFileName) {
    BufferedReader dataFile;
    String data;
    try {
        dataFile = new BufferedReader(new FileReader(inputFileName));
        data = dataFile.readLine();
        while (data != null) {
            if (!(data.startsWith("//") || data.matches("^\\s*$"))) {
                slideList.add(new SlideInfo(data.split("\\s*,\\s*")));
            }
            data = dataFile.readLine();
        }
        dataFile.close();
    } catch (FileNotFoundException e) {
        System.err.println("Unable to open file" + inputFileName);
        slideList = null;
    } catch (IOException e) {
        System.err.println("Error reading file " + inputFileName);
        slideList = null;
    }
}

Creating the Output Document

The setupOutputDocument() method starts by calling newPresentationDocument() to create an ODF presentation document from a template that is built into the library. Once you have the document, the method gets the the Document Object Model (a subclass of Document) for the content.xml and the <office:presentation> element that is the content root. All of the data that make up the document’s content will be children of this element.

void setupOutputDocument() {
    try {
        outputDocument =
            OdfSpreadsheetDocument.newSpreadsheetDocument();
        contentDom = outputDocument.getContentDom();
        contentAutoStyles = contentDom.getOrCreateAutomaticStyles();
        officePresentation = outputDocument.getContentRoot();
    } catch (Exception e) {
        System.err.println("Unable to create output file.");
        System.err.println(e.getMessage());
        outputDocument = null;
    }
}

Adding Styles

Here is the code for adding the four styles that this program needs. The OdfStyleGraphicProperties.Clip property is a rectangle that tells how much of the image to clip. The values are for the top, right, bottom, and left side of the image (clockwise from 12:00). In this case, you want the entire picture, so all four values are set to zero.

void addAutomaticStyles() {
    OdfStyle style;

    /* The style for the slide title */
    style = contentAutoStyles.newStyle(OdfStyleFamily.Presentation);
    slideTitleStyleName = style.getStyleNameAttribute();
    style.setProperty(OdfStyleGraphicProperties.AutoGrowHeight, "true");
    style.setProperty(OdfStyleGraphicProperties.MinHeight, "3cm");
    style.setProperty(OdfStyleTextProperties.FontFamily, "Helvetica");
    style.setProperty(OdfStyleTextProperties.FontSize, "44pt");

    /* Style for the image element */
    style = contentAutoStyles.newStyle(OdfStyleFamily.Graphic);
    imageStyleName = style.getStyleNameAttribute();
    style.setProperty(OdfStyleGraphicProperties.MinHeight, "10cm");
    style.setProperty(OdfStyleGraphicProperties.Clip,
        "rect(0cm, 0cm, 0cm, 0cm)");

    /* Graphic style for the table rectangles */
    style = contentAutoStyles.newStyle(OdfStyleFamily.Graphic);
    fauxTableRectangleStyleName = style.getStyleNameAttribute();
    style.setProperty(OdfStyleGraphicProperties.FillColor,
        "#ffffff");
    style.setProperty(OdfStyleGraphicProperties.TextareaVerticalAlign,
        "middle");
    style.setProperty(OdfStyleGraphicProperties.PaddingLeft, "8pt");

    /* Style for the text in the table */
    style = contentAutoStyles.newStyle(OdfStyleFamily.Paragraph);
    fauxTableTextStyleName = style.getStyleNameAttribute();
    style.setProperty(OdfStyleTextProperties.FontFamily, "Helvetica");
    style.setProperty(OdfStyleTextProperties.FontSize, "16pt");
}

Creating the Slides

The beginning of the createPages() method opens a Scanner to read the input file. If it can do so successfully, it sets a counter variable lets you give consecutive style names to each of the slides. If a line starts with // it’s a comment. Otherwise, we split the line on commas. If Thee

   void createPages(String inputFileName) {

        try (Scanner slideInfoScanner = new Scanner(new File(inputFileName))) {
            int counter = 1;
            while (slideInfoScanner.hasNextLine()) {
                String info = slideInfoScanner.nextLine();
                if (!info.startsWith("//")) {
                    String[] parts = info.split(",");
                    if (parts.length == 4) {
                        createOnePage(parts, counter);
                    } else {
                        System.err.println("\"" + info + "\" does not have the correct number of items.");
                    }
                    counter++;
                }
            }
        } catch (Exception e) {
                // do nothing; no pictures will be added.
        }
    }

Here is the code for creating one page, given the slide information and a counter. The newDrawPageElement requires the name of a “master page,” but you may safely leave this as the empty string or null. The setDrawNameAttribute sets an attribute used for referencing graphic elements.

    void createOnePage(String[] info, int counter) {

        DrawPageElement page = officePresentation.newDrawPageElement(null);
        page.setDrawNameAttribute("pg" + Integer.toString(counter));

Since the title is text rather than a graphic primitive such as a line, it must be placed inside a DrawFrameElement. The title’s position and dimensions are set in the frame as the the x, y, width and height. In the XML, these attributes are borrowed from the SVG (Scalable Vector Graphics) namespace, hence the Svg prefix.

    /* Create the title in a <draw:frame> element */
    DrawFrameElement frame = page.newDrawFrameElement();
    frame.setPresentationStyleNameAttribute(slideTitleStyleName);
    frame.setPresentationClassAttribute(
        PresentationClassAttribute.Value.TITLE.toString());
    frame.setSvgXAttribute("1.4cm");
    frame.setSvgYAttribute("0.85cm");
    frame.setSvgWidthAttribute("25cm");
    frame.setSvgHeightAttribute("3.5cm");

Inside the frame is a <draw:text-box> element containing a <text:p>.

    DrawTextBoxElement textBox = frame.newDrawTextBoxElement();
    TextPElement para = textBox.newTextPElement();
    para.setTextContent(info[0]);

Next, the page needs the image. It also is inside a frame, whose dimensions we specify. This is in a try/catch block.

        frame = page.newDrawFrameElement();
        frame.setPresentationStyleNameAttribute(imageStyleName);
        frame.setPresentationClassAttribute(
            PresentationClassAttribute.Value.GRAPHIC.toString());
        frame.setSvgXAttribute("14cm");
        frame.setSvgYAttribute("6cm");

We now create an OdfDrawImage. The newImage() method inserts the image into the document’s Pictures directory. It returns the internal document path name. We then use setXlinkHrefAttribute() to connect the draw:image element to that path name, and add the image to the frame with appendChild().

        OdfDrawImage image = new OdfDrawImage(contentDom);
        String imgPath = image.newImage(new URI(info[3]));
        image.setXlinkHrefAttribute(imgPath);
        frame.appendChild(image);

Because we want all the images to have the same width (12cm), we must read the image again with ImageIO.read to get its dimensions and calculate the correct height for the image.

    BufferedImage buffer = ImageIO.read(new File(info[3]));
    frame.setSvgWidthAttribute("12cm");
    frame.setSvgHeightAttribute(Double.toString(
        (12.0 * buffer.getHeight()) / buffer.getWidth())
            + "cm");

Finally, each page has to have the rectangles with the country and city names. This task is delegated to a separate method:

    page.appendChild(createDrawnTable(info));
    }
}

Labelled Rectangles

The code to create the “table” creates an <draw:g> group of the four rectangles and returns that group to be inserted into the page. If you open the resulting file in LibreOffice and click the table, you will be able to ungroup it into its component rectangles.

The createDrawnTable() in turn calls the helper method createTextRectangle(), whose parameters are as described in the javadoc.

private OdfDrawGroup createDrawnTable(SlideInfo info) {
    OdfDrawGroup group = new OdfDrawGroup(contentDom);
 
    group.appendChild(createTextRectangle(
        "Country", "1.5cm", "6cm", "4.7cm", "1cm"));
    group.appendChild(createTextRectangle(
        info.getCountry(), "6.2cm", "6cm", "7cm", "1cm"));
    group.appendChild(createTextRectangle(
        "City", "1.5cm", "7cm", "4.7cm", "1cm"));
    group.appendChild(createTextRectangle(
        info.getCity(), "6.2cm", "7cm", "7cm", "1cm"));
    return group;
}

/*
 * Create a rectangle that contains text.
 * @param textValue the text to display inside the rectangle
 * @param x coordinate of left corner of rectangle
 * @param y coordinate of upper corner of rectangle
 * @param width width of rectangle
 * @param height height of rectangle
 */
private DrawRectElement createTextRectangle(String textValue, String x, String y,
    String width, String height) {
    DrawRectElement r = new DrawRectElement(contentDom);
    OdfTextParagraph p;

    r.setSvgXAttribute(x);
    r.setSvgYAttribute(y);
    r.setSvgWidthAttribute(width);
    r.setSvgHeightAttribute(height);
    r.setDrawStyleNameAttribute(fauxTableRectangleStyleName);

    p = new OdfTextParagraph(contentDom);
    p.setStyleName(fauxTableTextStyleName);
    p.appendChild(contentDom.createTextNode(textValue));
    r.appendChild(p);
    return r;
}

Saving the Output Document

This consists of only one line of actual code, surrounded by error handling.

void saveOutputDocument()
{
    try
    {
        outputDocument.save(outputFileName);
    }
    catch (Exception e)
    {
        System.err.println("Unable to save document.");
        System.err.println(e.getMessage());
    }
}

The Entire Program

You may download the files for this program.