Chapter 6. Drawings

Let’s take a break from pure text and numbers to investigate OpenDocument’s graphic elements. Before we get to the actual objects that you can draw on a page, we have to discuss the general format of the file.

Although the line is the simplest of the geometric figures, this explanation will be fairly lengthy, because we are defining our terms. Example 6.3, “Simple Horizontal Line” shows the <draw:line> elements for a horizontal line. The draw:style-name links this line with its style. The draw:layer attribute tells which workspace this line belongs to. The svg:x1, svg:y1, svg:x2, and svg:y2 attributes define the beginning and ending points of the line as length specifiers. The <draw:text-style-name> attribute isn’t used in this example.

That’s all you need to define any sort of ordinary line; the different types of lines (arrows, colors, line style and width) are all handled in the style that is referenced by draw:style-name

Example 6.4, “Style for a Simple Line” shows the style for the preceding line. All it does is reference the default style and add two attributes that refer to text (which we will discuss in the section called “Attaching Text to a Line”).

To change the thickness of a line, add an svg:stroke-width attribute to the <style:graphic-properties>. This attribute is given as a number followed by a length specification, such as 0.12cm. The line’s color is controlled by the svg:stroke-color, given as a six-digit hexadecimal value such as #ff0000.

To make a dashed line, you add a draw:stroke-dash attribute; its value is the value of a <draw:stroke-dash> element which resides in the styles.xml file. Example 6.5, “Non-continuous Line” shows the XML for one of the standard dashed lines.

A dashed line is composed of alternating sequences of dots. draw:dots1 and draw:dots1-length give the number of dots and the length of the first sequence.[7] draw:dots2 and draw:dots2-length give the number of dots and length of the second sequence, and draw:distance gives the distance between the sequences. Finally, the draw:style tells whether the points of the dots and dashes are rect (square) or round, although, frankly, it’s hard to tell the difference.

There are times you want text to be “attached” to the line as a label so that moving the line moves the text along with it. Figure 6.2, “Arrow Labelled with Text” shows just such an arrow.[8]

Example 6.8, “XML for Labelled Arrow” shows the XML for the preceding figure.

As you may have suspected, the styles contain all the important information. Example 6.9, “Styles for Labelled Line” shows all the styles referenced in the preceding example.

1 draw:textarea-horizontal-align and draw:textarea-vertical-align place the text in relation to the area occupied by the line or arrow. The horizontal align can have values left, center, and right. The vertical align can have values top, middle, and bottom. The fo:padding-side attributes position the text relative to the line.
2 This style combines the attributes of the following two styles; if you are creating a document programmatically, you don’t have to create this style or reference it in the <draw:line> element.
3 This style is necessary to set the font size properly.

Let’s move on to two-dimensional objects: rectangles and squares, ellipses, circles, and arcs. Before looking at the individual shapes, it is necessary to discuss the style information they share.

Two-dimensional objects have an outline and an interior, so their styles contain information about the lines that make up the outline and about the method used to fill the object’s interior.

The styles for the outline are exactly the ones described in the section called “Line Attributes”. The interior style is specified with the draw:fill attribute, which may have a value of none, solid, gradient, hatch, or bitmap. If you want an unfilled object, you must specify none. If you leave off this attribute, the object will be filled with the default color (which is set in the <style:style style:name="standard" style:famliy="graphic"> element in styles.xml).

To fill an area with a gradient, set the draw:fill attribute to gradient, and set the draw:fill-gradient-name attribute to refer to a <draw:gradient> element in the styles.xml file.[9]

The <draw:gradient> element will have the corresponding draw:name attribute (with hex characters encoded as _xx_), a draw:display-name, and a draw:style attribute that specifies the gradient type: linear (from edge to edge), axial (from center outwards), radial (circular), ellipsoid, square, and rectangular.

All gradients have a draw:start-color and draw:end-color attribute. You may specify their intensity with the draw:start-intensity and draw:end-intensity attributes, each of which has values ranging from 0% to 100%; the default is 100%.

All gradients also have a draw:border attribute, again expressed as a percentage, which is used to scale a border which is filled by the start or end color only. The default is 0%. Figure 6.3, “Effect of Border on Gradients” shows two radial gradient fills. The gradient on the left has no border; the gradient on the right has a border of 50%.

All gradients may specify a draw:angle attribute that rotates the gradient. Figure 6.4, “Effect of Angle on Gradients” shows two axial gradients; the one on the left has an angle of zero, and the one on the right has an angle of 30 degrees. Angles are measured counterclockwise from 0 degrees, which is at the ”three o’clock” position. The value is expressed in tenths of a degree, so the value for the rotated gradient would be 300. The default value is zero. In the case of a radial gradient, this attribute has no effect, since circles look alike at every angle.

Finally, for all gradients except linear and axial, you may specify the center x and y coordinate where the gradient starts. The draw:cx and draw:cy attributes have values ranging from 0% to 100%. Figure 6.5, “Effect of Center Coordinates on Gradients” shows a rectangular gradient with a center x and y of 0% on the left, and a center x of 25% and center y of 50% on the right.

Example 6.11, “XML Representation of Gradients” shows the XML for the gradients in the preceding figures. The intensity attributes have been removed to save space.

Example 6.11. XML Representation of Gradients

<!-- radial gradient, light gray to white, centered, 0% border -->
<draw:gradient draw:name="radial_20_borderless"
    draw:display-name="radial borderless"
    draw:style="radial"
    draw:cx="50%" draw:cy="50%"
    draw:start-color="#999999" draw:end-color="#ffffff"
    draw:border="0%"/>
 
 <!-- radial gradient, gray to white, centered, 50% border -->
 <draw:gradient draw:name="radial_20_bordered"
    draw:display-name="radial bordered"
    draw:style="radial"
    draw:cx="50%" draw:cy="50%"
    draw:start-color="#666666" draw:end-color="#ffffff"
    draw:start-intensity="100%" draw:end-intensity="100%" draw:border="50%"/

<!-- axial gradient, black to white, 30% border -->
<draw:gradient draw:name="axial_20_black_20_to_20_white"
    draw:display-name="axial black to white"
    draw:style="axial"
    draw:start-color="#000000" draw:end-color="#ffffff"
    draw:angle="0" draw:border="30%"/>

<!-- axial gradient, black to white, 30% border, rotated 30 degrees -->
<draw:gradient draw:name="axial_20_30_20_degree_20_black_20_to_20_white"
    draw:display-name="axial 30 degree black to white"
    draw:style="axial"
    draw:start-color="#000000" draw:end-color="#ffffff"
    draw:angle="300" draw:border="30%"/>

<!-- rectangular gradient, gray to white, centered at top left -->  
<draw:gradient draw:name="rectangular_20_0_20_0"
    draw:display-name="rectangular 0 0"
    draw:style="rectangular"
    draw:cx="0%" draw:cy="0%"
    draw:start-color="#808080" draw:end-color="#ffffff"
    draw:angle="0" draw:border="0%"/>

<!-- rectangular gradient, gray to white, centered at (25%, 50%) -->
<draw:gradient draw:name="rectangular_20_25_20_50"
    draw:display-name="rectangular 25 50"
    draw:style="rectangular"
    draw:cx="25%" draw:cy="50%"
    draw:start-color="#808080" draw:end-color="#ffffff"
    draw:angle="0" draw:border="0%"/>

If you want anything other than a basic shape, you have to use a polyline, polygon, or free-form curve. For all of these, you must establish a bounding box and a coordinate system. The bounding box is a rectangle which entirely contains the object, and is specified with svg:x, svg:y, svg:width, and svg:height attributes. all measured in centimeters.

The coordinate system is established with the svg:viewBox attribute which has four numbers: the minimum x-coordinate, minimum x-coordinate, width, and height of the coordinate system. In OpenDocument, the minimum coordinates are zero, and the width and height are set to the width and height of the bounding box times 1000. Thus, a coordinate of (250, 1602) will be 0.25 centimeters from the left corner of the bounding box and 1.602 centimeters from the top of the bounding box.

A polyline is simply a series of connected straight lines. Their coordinates are described as pairs of x,y coordinates separated by whitespace. The polyline shown in Figure 6.6, “Simple Polyline” has the XML representation in Example 6.17, “XML for a Polyline”.

Polygons use the <draw:polygon> element, which has exactly the same attributes as <draw:polyline>. You do not have to close the polygon by repeating the first coordinate at the end of the draw:points attribute; the application will automatically close the figure for you when it draws the polygon.

For all other curves (Bézier curves and freeform drawing), OpenDocument uses a <draw:path> element, which is modeled after the SVG <path> element. The svg:d attribute describes the path data, and has a value consisting of command letters and coordinates. For example, the M command moves the pen to the given x- and y-coordinates. L draws a line to the given coordinates, and Z is the command for closing a polygon. Thus, the path data for a trapezoid would look like this:

svg:d="M 300 0  L 700 0 L 1000 1000 L 0 1000 Z"

Lowercase letters take relative coordinates (relative to the last point drawn). Thus, the preceding trapezoid, expressed in relative coordinates, is:

svg:d="m 300 0  l 400 0 l 300 1000 l -1000 0 z"

In the preceding example, the first coordinate is relative to (0,0), the upper left of the bounding box. If you eliminate duplicated commands and unnecessary whitespace, the preceding examples can be reduced to:[11]

svg:d="M300 0L700 0 1000 1000 0 1000Z"
svg:d="m300 0l400 0 300 1000-1000 0z"

Table 6.1, “Path Commands” summarizes the path commands available in OpenDocument. This table is taken from SVG Essentials, published by O’Reilly & Associates. When creating paths, uppercase commands use absolute coordinates and lowercase commands use relative coordinates. Some applications may support only a subset of these commands.

Table 6.1. Path Commands

CommandArgumentsEffect
M
m
x yMove to given coordinates.
L
l
x yDraw a line to the given coordinates. You may supply multiple sets of coordinates to draw a polyline.
H
h
xDraw a horizontal line to the given x-coordinate.
V
v
yDraw a vertical line to the given y-coordinate.
A
a
rx ry x-axis-rotation large-arc sweep x yDraw an elliptical arc from the current point to (xy). The points are on an ellipse with x-radius rx and y-radius ry. The ellipse is rotated x-axis-rotation degrees. If the arc is less than 180 degrees, large-arc is zero; if greater than 180 degrees, large-arc is one. If the arc is to be drawn in the positive direction, sweep is one; otherwise it is zero.
Q
q
x1 y1 x yDraw a quadratic Bézier curve from the current point to (xy) using control point (x1y1).
T
t
x yDraw a quadratic Bézier curve from the current point to (xy). The control point will be the reflection of the previous Q command's control point. If there is no previous curve, the current point will be used as the control point.
C
c
x1 y1 x2 y2 x yDraw a cubic Bézier curve from the current point to (xy) using control point (x1y1) as the control point for the beginning of the curve and (x2y2) as the control point for the endpoint of the curve.
S
s
x2 y2 x yDraw a cubic Bézier curve from the current point to (xy), using (x2y2) as the control point for this new endpoint. The first control point will be the reflection of the previous C command's ending control point. If there is no previous curve, the current point will be used as the first control point.

Text in a graphic is contained in a <draw:frame> element. the drawing style. As with other objects, it has a draw:layer of layout and sets the location and dimensions with svg:x, svg:y, svg:width, and svg:height. The <draw:frame> also contains a draw:style-name attribute that refers to a style:style element in the content.xml file. This <style:style> contains a <style:graphic-properties> element that sets

You may specify how the text area grows to fit the text with the draw:auto-grow-height and draw:auto-grow-width attributes, which take values of true and false.

Within the <draw:frame> is a <draw:text-box> element, which contains one or more <text:p> elements that describe the actual text. Example 6.18, “Simple Text Box” shows the XML fragments for a simple text area that displays the words Hello, text. on a peach-colored background with a purple border.

A callout is implemented via a <draw:caption> element. In addition to all the attributes of the draw:frame element, it specifies the endpoint of the callout line with the draw:caption-point-x and draw:caption-point-y attributes. Their values are relative to the upper left corner of the callout’s bounding box. Example 6.19, “XML for Callout Text” shows the style and body XML for the callout depicted in Figure 6.7, “Callout Text in a Drawing”. (Just the callout, not the circle.) The arrow is considered to be the draw:marker-end.

We now have enough information to create a drawing from scratch. We will use XSLT to create an OpenDocument drawing from data in the Weather Observation Markup Format (OMF), defined at http://zowie.metnet.navy.mil/~spawar/JMV-TNG/XML/OMF.html. OMF is, for the most part, a wrapper for several different types of weather reports. The OMF elements add annotation, decoded information, and quantities calculated from the raw reports. Here is a sample report:

<Reports TStamp="997568716">
<SYN Title='AAXX' TStamp='997573600' LatLon='37.567, 126.967' BId='471080'
SName='RKSL, SEOUL' Elev='86'>
<SYID>47108</SYID>
<SYG T='22.5' TD='14.1' P='1004.1' P0='1014.1' Pd='0 0.1' Vis='22000'
Ceiling='INF' Wind='30-70, 1.5' WX='NOSIG' Prec=' ' Clouds='44070'>
32972 40703 10225 20141 30041 40141 50001 84070
</SYG></SYN>
</Reports>

Our objective is to extract the reporting station, the date and time, temperature, wind speed and direction, and visibility from the report. These data will be filled into the graphic template of Figure 6.8, “Graphic weather template”

The OMF format attributes that we’re interested in are listed here, along with the plan for displaying them in the final graphic. The first two required attributes come from the <SYN> element, the rest are optional attributes from its child <SYG> element.

Let us first examine the styles we will need for this drawing:

This gives us enough to start our stylesheet. [The full transformation is file weather.xsl in directory ch06 in the downloadable example files.] We start out with the root element and its namespace declarations.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
    xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"
    xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0"
    xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"
    xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0"
    xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"
    xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"

    xmlns:label="urn:evc-cit.info:examples:internal-use-only"
>

Whoa! That last namespace isn’t one that you have seen before. We’re going to insert some custom elements of our own into this stylesheet to make it easier to put labels on the graphics. These elements will need a namespace, so we have made up a prefix (label) and a URI for them (urn:evc-cit.info:examples:internal-use-only). A URI just needs to be unique; it doesn’t have to point to anything on the web—and in this case, we don’t want it to.

Now, on to the rest of the setup:

<xsl:output method="xml" indent="yes"/>

<xsl:template match="/">
<office:document-content
    xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
    xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0"
    xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"
    xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0"
    xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"
    xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"
    xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"
    office:version="1.0">
<office:scripts/>

<office:automatic-styles>
    <style:style style:name="dp1" style:family="drawing-page"/>
    
    <style:style style:name="solidLine" style:family="graphic"
     style:parent-style-name="standard">
        <style:graphic-properties
            svg:stroke-color="#000000"
            draw:fill="none"/>
    </style:style>

    <draw:stroke-dash draw:name="3 by 3" draw:style="rect"
        draw:dots1="3" draw:dots1-length="0.203cm"
        draw:dots2="3" draw:dots2-length="0.203cm"
        draw:distance="0.203cm"/>
    
    <style:style style:name="dashLine" style:family="graphic"
     style:parent-style-name="standard">
        <style:graphic-properties
            draw:stroke="dash" draw:stroke-dash="3 by 3"
            svg:stroke-color="#000000"
            draw:fill="none"/>
    </style:style>

    <style:style style:name="redfill" style:family="graphic"
     style:parent-style-name="standard">
        <style:graphic-properties draw:stroke="none"
            draw:fill="solid" draw:fill-color="#ff0000"/>
    </style:style>

    <style:style style:name="bluefill" style:family="graphic"
     style:parent-style-name="standard">
        <style:graphic-properties draw:stroke="none"
            draw:fill="solid" draw:fill-color="#0000ff"/>
    </style:style>

    <style:style style:name="greenfill" style:family="graphic"
     style:parent-style-name="standard">
        <style:graphic-properties draw:stroke="none"
            draw:fill="solid" draw:fill-color="#008000"/>
    </style:style>
    
    <style:style style:name="textgraphics" style:family="graphic"
      style:parent-style-name="standard">
        <style:graphic-properties draw:stroke="none" draw:fill="none"
            draw:auto-grow-width="true" draw:auto-grow-height="true"/>
    </style:style>

    <style:style style:name="smallLeft" style:family="paragraph">
        <style:text-properties
            fo:font-size="10pt"
            fo:font-family="Bitstream Vera Sans"/>
    </style:style>

    <style:style style:name="smallRight" style:family="paragraph">
        <style:paragraph-properties fo:text-align="end"/>
        <style:text-properties
            fo:font-size="10pt"
            fo:font-family="Bitstream Vera Sans"/>
    </style:style>
    
    <style:style style:name="smallCenter" style:family="paragraph">
        <style:paragraph-properties fo:text-align="center"/>
        <style:text-properties
            fo:font-size="10pt"
            fo:font-family="Bitsream Vera Sans"/>
    </style:style>
    
    <style:style style:name="largetext" style:family="paragraph">
        <style:text-properties
            fo:font-size="12pt"
            fo:font-family="Bitstream Vera Sans"/>
    </style:style>

</office:automatic-styles>

<office:body>
    <office:drawing>
        <draw:page draw:name="page1" draw:style-name="dp1"
            draw:master-page-name="Default">
            <xsl:apply-templates select="Reports"/>
        </draw:page>
    </office:drawing>
</office:body>
</office:document-content>  
</xsl:template>

<!-- other templates go here -->
</xsl:stylesheet>

To make things modular (and to keep our sanity), we will handle the four objects in separate templates, from easiest to most difficult.

<xsl:template match="Reports">
    <xsl:apply-template select="SYN/@SName"/>
    <xsl:apply-template select="SYN/SYG/@Vis"/>
    <xsl:apply-template select="SYN/SYG/@Wind"/>
    <xsl:apply-template select="SYN/SYG/@T"/>
</xsl:template>

On to the visibility bar. To make the math easy, we’ll make the bar eight centimeters wide and one centimeter tall.

<xsl:template match="@Vis">

<!-- calculate the number of centimeters to fill -->
<xsl:variable name="visWidth"> 1
    <xsl:choose>
        <xsl:when test=". = 'INF'">8</xsl:when>
        <xsl:when test=". &gt; 40000">8</xsl:when>
        <xsl:otherwise>
            <xsl:value-of select=". * 8 div 40000.00"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:variable>

<!-- draw the filled area --> 2
<draw:rect draw:style-name="greenfill" draw:layer="layout"
    svg:x="6cm" svg:y="2cm"
    svg:width="{$visWidth}cm" svg:height="1cm" />

<!-- draw the empty bar -->
<draw:rect draw:style-name="solidLine" draw:layer="layout"
    svg:x="6cm" svg:y="2cm"
    svg:width="8cm" svg:height="1cm" />

<!-- tick marks --> 3
<draw:path draw:style-name="solidLine" draw:layer="layout"
    svg:x="8cm" svg:y="3cm" svg:width="8cm" svg:height="1cm"
    svg:viewBox="0 0 8000 1000"
    svg:d="m 0 0 l 0 125 m 2000 -125 l 0 125 m 2000 -125 l 0 125"/> 

<!-- create text under the tick marks --> 4
<xsl:for-each select="document('')/*/label:visibility/label:text">
    <draw:frame draw:style-name="textgraphics" draw:layer="layout"
        svg:y="3.125cm"
        svg:width="0.8cm" svg:height=".5cm">
        <xsl:attribute name="svg:x">
            <xsl:value-of select="@x + 5.625"/>
            <xsl:text>cm</xsl:text>
        </xsl:attribute>
        <draw:text-box>
            <text:p text:style-name="smallCenter"><xsl:value-of
            select="."/></text:p>
            </draw:text-box>
    </draw:frame>
</xsl:for-each>

<draw:frame draw:style-name="textgraphics" 5
    draw:layer="layout"
    svg:x="6cm" svg:y="3.7cm" svg:height="1cm" svg:width="8cm">
    <draw:text-box>
    <text:p text:style-name="smallCenter">Visibility (m)</text:p>
    <text:p text:style-name="smallCenter"><xsl:value-of select="."/></text:p>
    </draw:text-box>
</draw:frame>

</xsl:template>

<label:visibility> 6
    <label:text x="0">0</label:text>
    <label:text x="2">10</label:text>
    <label:text x="4">20</label:text>
    <label:text x="6">30</label:text>
    <label:text x="8">40+</label:text>
</label:visibility>
1 To prevent the filled area from overflowing the boundary, we have to test to see if we have the special value of INF (unlimited visibility) or a value greater than 40,000. If so, fill all eight centimeters of the bar.
2 If object A’s XML comes before object B’s XML in the file, then object A is behind object B. Thus, we draw the filled-in area first. This guarantees that it will not obscure the outline of the visibility bar.
3 We start the x-coordinate at 8 centimeters, where the first tick mark lies. This is because OpenOffice.org became cranky when we set the x-coordinate to 10 centimeters and started the path with m2000 0, insisting upon moving it back to (0,0).
4 Each of the numbers underneath the visibility bar requires a <draw:frame> element. Each of these elements has a large number of attributes, differing only in the svg:x value. Copy-and-paste will work, but if a modification of the drawing required a change to one of the other attributes, we would have to change all the elements.

What we will do is keep a table of the labels’ text and their offset from the starting x-position, and then use XSLT to process that table. This table will be stored in the form of elements with the label: namespace.

In order to access this information, we use the XPath expression document('')/*/label:visibility/label:text. It will select all <label:text> elements within <label:visiblity> elements within this entire document (document('')/*).

5 We finish the template with the words “Visibility (m)” and the visiblity value in a framed text box.
6 Here are the labels and their offsets that the template uses. The offsets are in centimeters.

We now move on to the wind compass; most of the complexity in this template lies in determining whether we need to draw one line or two within the compass.

<xsl:template match="@Wind">
<draw:circle draw:style-name="solidLine" draw:layer="layout"
    svg:x="6cm" svg:y="5.5cm"
    svg:width="4cm" svg:height="4cm"/>

<xsl:variable name="dir" select="substring-before(., ',')"/> 1
<xsl:variable name="dir1">
    <xsl:choose>
    <xsl:when test="contains($dir, '-')"> 2
        <xsl:value-of select="90 - number(substring-before($dir, '-' ))"/>
    </xsl:when>
    <xsl:otherwise>
        <xsl:value-of select="90 - number($dir)"/>
    </xsl:otherwise>
    </xsl:choose>
</xsl:variable>

<xsl:variable name="dir2">  3
    <xsl:choose>
    <xsl:when test="contains($dir, '-')">
        <xsl:value-of select="90 - number(substring-after($dir, '-' ))"/>
    </xsl:when>
    <xsl:otherwise>
        <xsl:value-of select="90 - number($dir)"/>
    </xsl:otherwise>
    </xsl:choose>
</xsl:variable>

<draw:path 4
    draw:layer="layout"
    svg:viewBox="0 0 2000 100"
    svg:d="m 0 0 2000 0"
    svg:width="2cm" svg:height="0.1cm"
    svg:x="0cm" svg:y="0cm"
    draw:transform="rotate({$dir1 * 3.14159 div 180})
     translate(8cm 7.5cm)"
    draw:style-name="solidLine"/>

<xsl:if test="$dir1 != $dir2">
    <draw:path draw:style-name="dashLine" 5
        draw:layer="layout"
        svg:viewBox="0 0 2000 100"
        svg:width="2cm" svg:height="0.1cm"
        svg:x="0cm" svg:y="0cm"
        svg:d="m 0 0 2000 0"
        draw:transform="rotate({$dir2 * 3.14159 div 180})
         translate(8cm 7.5cm)"/>
</xsl:if>

<!-- tick marks -->
<draw:path draw:style-name="solidLine" draw:layer="layout"
    svg:x="6cm" svg:y="5.5cm" svg:width="4.5cm" svg:height="4.5cm"
    svg:viewBox="0 0 4500 4500"
    svg:d="M 2000 0 v 125 M 4000 2000 h -125 M 2000 4000 v -125 M 0 2000 h 125"/> 

<!-- create text near the tick marks --> 6
<xsl:for-each select="document('')/*/label:compass/label:text">
    <draw:frame draw:style-name="textgraphics" draw:layer="layout"
        svg:width="0.75cm" svg:height=".5cm">
        <xsl:attribute name="svg:x"><xsl:value-of
            select="5.625 + @x"/>cm</xsl:attribute>
        <xsl:attribute name="svg:y"><xsl:value-of
            select="5.25 + @y"/>cm</xsl:attribute>
        <draw:text-box>
            <text:p text:style-name="smallCenter"><xsl:value-of
                select="."/></text:p>
        </draw:text-box>
    </draw:frame>
</xsl:for-each>

<!-- text under the wind compass -->
<draw:frame
    draw:layer="layout"
    svg:x="6cm" svg:y="10cm" svg:height="1cm" svg:width="4cm">
    <draw:text-box>
        <text:p text:style-name="smallCenter">Wind Speed (m/sec)</text:p>
        <text:p text:style-name="smallCenter"><xsl:value-of
            select="$dir"/></text:p>
    </draw:text-box>
</draw:frame>
</xsl:template>

<label:compass> 7
    <label:text x="2" y="-0.25">N</label:text>
    <label:text x="4.375" y="2">E</label:text>
    <label:text x="2" y="4.25">S</label:text>
    <label:text x="-0.375" y="2">W</label:text>
</label:compass>
1 This gets the direction part of the wind information by grabbing the substring before the separating comma.
2 If there is a hyphen in the direction, then it must be split into two portions. This sets the first number. The weather report makes north zero degrees and east 90 degrees; OpenDocument says that east is zero and north is 90; hence the formula 90 - n. If there’s no hyphen, then the first direction is simply offset by -90 degrees. The number function ensures that string data will be converted to numeric form after stripping leading and trailing whitespace.
3 Similar code sets the second direction. You may wonder why we didn’t use simpler code like this:
<xsl:choose>
<xsl:when test="contains($dir,'-')">
    <xsl:variable name="dir1"> ... </xsl:variable>
    <xsl:variable name="dir2"> ... </xsl:variable>
</xsl:when>
<xsl:otherwise>
    <xsl:variable name="dir1"> ... </xsl:variable>
    <xsl:variable name="dir2"> ... </xsl:variable>
</xsl:otherwise>
</xsl:choose>

Because a variable exists only within its enclosing block, this code won’t work—the <xsl:when> or <xsl:otherwise> would create the variables, which would disppear immediately upon encountering the ending </xsl:when> or </xsl:otherwise>. This is why we have to repeat all the choice code within the variable declarations.

4 This draws the first line; we are guaranteed at least one line. Note the conversion of degrees to radians. To make the transformation easier, we draw the line starting at (0,0), rotate it around the “origin,” and then move the rotated line to its final position. The first line is always a solid line.
5 The second line, if required, is always a dashed line.
6 We use the same trick of accessing our “lookup table” for the labels and their relative positions.
7 In these elements, as previously, the offsets are in centimeters.

Finally, the thermometer. We are drawing the thermometer in two different segments: the straight part and the bulb. We will use a <draw:circle> for the bulb because OpenOffice.org doesn’t let us use a <draw:path> with the A (arc) command, and, frankly, the math for a cubic Bézier curve that approximates a circular arc was something that I was totally unwilling to attempt.

<xsl:template match="@T">

<xsl:variable name="fillcolor"> 1
    <xsl:choose>
        <xsl:when test=". &lt; 0">bluefill</xsl:when>
        <xsl:otherwise>redfill</xsl:otherwise>
    </xsl:choose>
</xsl:variable>

<!-- fill the straight part of the thermometer -->
<xsl:variable name="h" select="50*(. + 40)"/> 2
<draw:polygon draw:style-name="{$fillcolor}" draw:layer="layout"
    svg:x="2.25cm" svg:y="{7.5 - $h div 1000}cm"
    svg:width="0.5cm" svg:height="{$h div 1000}cm"
    svg:viewBox="0 0 500 {$h}"
    draw:points="0,{$h} 0,0 500,0 500,{$h}" />

<!-- fill the bulb -->
<draw:circle draw:style-name="{$fillcolor}" draw:layer="layout"
    draw:kind="cut"
    draw:start-angle="117" draw:end-angle="63"
    svg:x="2cm" svg:y="7.415cm"
    svg:width="1cm" svg:height="1cm"/>

<!-- draw straight part of thermometer --> 3
<draw:polyline draw:style-name="solidLine" draw:layer="layout"
    svg:x="2.25cm" svg:y="3cm"
    svg:width="0.6cm" svg:height="4.6cm"
    svg:viewBox="0 0 600 4600"
    draw:points="0,4500 0,0 500,0 500,4500" />

<!-- draw the bulb outline -->
<draw:circle draw:style-name="solidLine" draw:layer="layout"
    draw:kind="arc"
    draw:start-angle="117" draw:end-angle="63"
    svg:x="2cm" svg:y="7.415cm"
    svg:width="1cm" svg:height="1cm"/>

<xsl:for-each select="document('')/*/label:thermometer/label:text"> 4
    <draw:frame draw:style-name="textgraphics" draw:layer="layout"
        svg:width="1cm" svg:height=".5cm">
        <xsl:attribute name="svg:x"><xsl:value-of
            select="1.4 + @x"/>cm</xsl:attribute>
        <xsl:attribute name="svg:y"><xsl:value-of
            select="3 + @y"/>cm</xsl:attribute>
        <draw:text-box>
            <text:p text:style-name="{@tstyle}"><xsl:value-of
                select="."/></text:p>
        </draw:text-box>
    </draw:frame>
</xsl:for-each>

<draw:frame draw:style-name="textgraphics"
    draw:layer="layout"
    svg:x="1cm" svg:y="8.5cm" svg:height="1cm" svg:width="3cm">
    <draw:text-box>
        <text:p text:style-name="smallCenter">Temp.</text:p>
        <text:p text:style-name="smallCenter"><xsl:value-of select="."/>
        <xsl:text> / </xsl:text>
        <xsl:value-of select="round((.  div 5) * 9 + 32)"/></text:p>
    </draw:text-box>
</draw:frame>
</xsl:template>

<label:thermometer>
    <label:text x="0" y="-0.175" tstyle="smallRight">50</label:text>
    <label:text x="0" y="2.075" tstyle="smallRight">0</label:text>
    <label:text x="0" y="4.1" tstyle="smallRight">-40</label:text>
    <label:text x="-0.5" y="4.5" tstyle="smallRight">C</label:text>
    <label:text x="1.5" y="-0.175" tstyle="smallLeft">120</label:text>
    <label:text x="1.5" y="2.075" tstyle="smallLeft">32</label:text>
    <label:text x="1.5" y="4.1" tstyle="smallLeft">-40</label:text>
    <label:text x="2" y="4.5" tstyle="smallLeft">F</label:text>
</label:thermometer>
1 Rather than repeat the code for selecting the appropriate color, we store it in a variable.
2 Lots of ugly math here, all to make sure that we make the polygon start at the (0,0) point. This formula will produce ugly results for temperatures above 50 and below -40 celsius.
3 Again, we draw the outlines after the filled areas to ensure that they are always visible.
4 Once again, we process <label:text> elements to make our work easier. The x and y attributes are in centimeters, and the tstyle attribute gives the text alignment as one of our pre-built styles.

The resulting diagram looks something like Figure 6.9, “Finished Weather Report Drawing”.

If it seems like there is a lot of work to create such a simple drawing, you’re right. That’s because each object in a drawing is self-contained and needs to carry a lot of information with it.

Before we move on to three-dimensional drawing, we need to cover two more two-dimensional constructs: grouping objects and drawing connectors.

A connector is a line that joins objects and remains attached when you move them. Connectors are attached to “glue points” on the object. The default glue points are at the 12:00, 3:00, 6:00 and 9:00 positions, and are numbered clockwise 0 to 3. Figure 6.10, “Default Glue Points” shows a rectangle with a connector attached to glue point 1.

Each object joined by a connector has a unique draw:id, which is of the XML “ID” type, which must begin with an alphabetic character.

Once the two objects to be joined have their identifying numbers, OpenDocument uses a <draw:connector> element with the following attributes that are shared with the <draw:line> element:

The following additional attributes complete the connection information:

Three-dimensional objects are contained in a <dr3d:scene> element. It has a large number of attributes that describe the scene’s shading and geometry. Within the scene are one or more <dr3d:light> elements, each one describing an illumination source, and one or more three-d shapes, which can be one of the following:

This element has the standard position-and-bounds attributes: svg:x, svg:y, svg:width, and svg:height. Along with these come the attributes specifically designed for three-dimensional objects. Many of these will specify vectors, which come in the form of an x-, y-, and z-coordinate in parentheses, separated by whitespace. OpenDocument’s coordinate system is a left-handed system: the positive z axis points out of the screen towards the person viewing the drawing. The values are measured in units of 1000 per centimeter.

Following the lighting is the description of the three-d object itself. Cubes and spheres have their own special elements: <dr3d:cube> and <dr3d:sphere>. These need no attributes other than the draw:layer and controling draw:style-name. All other objects are described by a generic <dr3d:rotate> element, which describes a path exactly as described in the section called “Polylines, Polygons, and Free Form Curves”, with a svg:viewBox and svg:d attribute.

The path for a cylinder is a rectangle. The path for a cone is a triangle; note that the triangle is “upside-down.” The path for a torus is a circle, the path for a shell is a chord, and the path for a half-sphere is a quarter-circle, as shown in Figure 6.12, “Path for a Cone, Shell, and Half-Sphere”. When you convert an arbitrary path to a rotation object, your path becomes the path of the <dr3d:rotate> object.

Example 6.21, “XML for a Cylinder” shows the XML for a cylinder; only the single enabled light source is included.

As with all other drawing objects, the presentation is affected by a <style:style> referred to by the object’s draw:style-name property. Both the scene and the object have a draw:style-name, but only the one attached to the object controls the presentation—the one attached to the scene is ignored.

As with two-dimensional objects, you specify the color with draw:fill-color for solid colors, draw:gradient for a color range, draw:hatch for patterns, or draw:fill-image for bitmaps.

In addition to the two-dimensional drawing information, you may apply the following three-dimensional attributes:

dr3d:horizontal-segments, dr3d:vertical-segments
The number of horizontal segments tells how many “slices” to stack up to create an object. The number of vertical segments tells how many degrees to take when it rotates an object. Figure 6.13, “Flat-shaded Spheres with Differing Numbers of Segments” shows a sphere drawn in two ways; the one on the left uses the default values of 24 horizontal segments and 24 vertical segments (i.e., rotation at 15 degree intervals).

The sphere on the right has only six horizontal segments and six vertical segments (rotation at 60 degree intervals). The figure uses flat shading to clearly display the difference. When using Gouraud shading, as shown in Figure 6.14, “Gouraud-shaded Spheres with Differing Numbers of Segments”, the default values assume a smooth spherical appearance.

Note

OpenOffice.org actually uses the same path information to generate a cone and a pyramid; the cone has 24 vertical segments and the pyramid has four, giving it a rectangular base.

dr3d:end-angle
This number, in tenths of a degree, tells where to stop the rotation about the y-axis. The default value is 3600. Rotation proceeds counterclockwise “into” the screen. Thus, a half-cone will show the sheared-off face with the rounded area behind it.
dr3d:back-scale
This attribute should be applied only to extruded objects. It gives the ratio of the size of the front of the object to the size of the back of the object. Thus, if the value is 50%, the front face of the object will be half the size of the back face.
dr3d:depth
This attribute’s value, given in centimeters (such as 1.25cm, tells the depth of an extruded object. It has no effect on rotated objects.
dr3d:normals-kind, dr3d:normals-direction
In these attributes, the word normal doesn’t mean “default”; it means “perpendicular to a surface.” The dr3d:normals-kind attribute tells how light reflects off an object. A value of flat shows individual polygons, sphere renders a smooth surface, and object chooses the best method depending upon the object. You may invert the light source (absorbing instead of reflecting) by setting <dr3d:normals-direction> to inverse The other value is normal, which in this case does mean “the ordinary way.”
dr3d:diffuse-color, dr3d:emissive-color, dr3d:specular-color
All of these attributes take a hexadecimal color specification as their value, and describe the behavior of the “material” that the the object is made of. dr3d:diffuse-color is the color of light from outside that scatters when reflected. dr3d:emissive-color is the color of light that the object itself emits. dr3d:specular-color is the color of reflective highlights.
dr3d:shininess
This is a percent value which appears to be implemented in the opposite way that its name implies. A shininess of 100% produces a non-reflective object; a shininess of 0% produces a highly reflective one.
dr3d:close-front, dr3d:close-back
Apply these boolean attributes to extruded objects. Specify true if you want the front and/or back face of the object to be displayed, false if you don’t.
dr3d:shadow
The default value for this attribute is hidden; if set to visible, a three-d shadow appears. This attribute overrides the draw:shadow attribute.
style:repeat
If you are using a bitmap for your object, you may set this attribute to repeat to tile the surface or stretch to wrap the object in the bitmap.
dr3d:texture-kind
Another attrbute for textured objects (bitmap fill) that seems to be contradictory: You may select color, which produces a black-and-white effect. Selecting luminance, the default, shows the bitmap in color.
dr3d:texture-mode
When set to blend, you see both the bitmap and the fill color (where the bitmap is transparent). When set to modulate, you see only the bitmap.
dr3d:texture-generation-mode-x, dr3d:texture-generation-mode-y
These attributes determine how the texture coordinates are generated when wrapping a bitmap around an object. The possible values are object (object-dependent), parallel, and sphere.

There are two other three-d attributes whose settings appear to have no effect on the picture: dr3d:backface-culling and dr3d:texture-filter. Both these attributes may either be enabled or disabled.



[7] If there is no length for the first sequence of dots, then they appear as points of minimal size.

[8] It is possible to achieve the same effect in a more difficult way: by grouping a separate text and line object.

[9] If you are creating a file on your own, you may put gradients in the content.xml file.

[10] All objects can also have a draw:text-style-name attribute if you attach text to them, as described in the section called “Attaching Text to a Line”, but that attribute is not included in these examples.

[11] The first L is also unnecessary; points after a first Move are presumed to be lines.


Copyright (c) 2005 O’Reilly & Associates, Inc. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License".