This is a tutorial on how to create stylesheets for use with pres2svg. It is a step-by-step guide on how to create a stylesheet with explanations and tips.
The source files used can be found under the examples/tutorial directory.
To create stylesheets, you need to know how to write SVG and how declarative animations work under SVG. It is also assumed that you know how to use JackSVG. This tutorial should be read in conjunction with the JackSVG Stylesheet Guide.
We shall start with the simplest stylesheet possible: an XML
document that just contains the stylesheet
element. The stylesheet has four mandatory attributes: version,
generate-heading-slides, width and height. Along with the necessary XML
namespace declarations, the minimal stylesheet file is:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE j:stylesheet SYSTEM "../schemas/jacksvg-stylesheet.dtd"> <j:stylesheet version="1.0" width="800" height="600" generate-heading-slides="none" xmlns:j="http://www.dstc.edu.au/ns/2002/JackSVG/stylesheet" xmlns="http://www.w3.org/2000/svg"> </j:stylesheet>
The dimensions of 800x600 gives us the desired aspect ratio for most PC computer screen (4:3). We could have easily used 1024x768, 400x300, 4x3, or even 1x0.75. Choose a set of dimensions that is easy to work with. If you are making presentations for monitors or screens with a different aspect ratio choose a different set of numbers.
The only case where the dimensions do matter is if your presentation has embedded images. The dimensions of the images will be placed into the coordinate system of the stylesheet. For example, an image that is 400x300 in size will fit into a quarter of an 800x600 stylesheet, but will appear smaller in a 1024x768 stylesheet. Of course, this is before any automatic scaling of the image to fit into the slide occurs.
The generate-heading-slides attribute is
set to "none" - no heading slides will be created from groups and
sections in the presentation file. This will be changed later in the
tutorial.
Try to apply the stylesheet to a presentation:
pres2svg -stylesheet step1.xml pres1.xml -output step1.svg
This operation fails. An error is raised because there are no slide masters for the presentation's slides to use. Since none of the presentation's slides have been tagged with an explicit class, they will all look for a slide master called "DEFAULT".
To remedy the above problem, a slide master is added to the stylesheet. From now on, we will not show the prologue of the XML file.
<j:stylesheet version="1.0" width="1024" height="768"
generate-heading-slides="none"
xmlns:j="http://www.dstc.edu.au/ns/2002/JackSVG/stylesheet"
xmlns="http://www.w3.org/2000/svg">
<j:master slide-class="DEFAULT"
number-buttons-prev="0"
number-buttons-next="0"
number-buttons-advance="0"
number-buttons-index="0">
</j:master>
</j:stylesheet>
The number-buttons-* attributes tells the
processor how many buttons we have defined on the slide master. This
mechanism will allow us later to have fancy slides with multiple buttons
that perform the same action. With the sample stylesheets, we often have
two sets of buttons: one visible for new users and a larger invisible
region for use during a live presentation (where you don't want to be
trying to aim the mouse correctly over a small button). Currently they
are all set to zero, but later on (when we do add some buttons) they
will be set to non-zero values.
Running this through the program now produces no errors, and will generate a SVG file.
pres2svg -s step1.xml pres1.xml -o step1.svg
However, viewing the SVG file produces a totally blank page. That is because the slide master has not specified anything to be drawn, so nothing is placed on the slide.
First, we will add a text block to show the slide's title. This block must be called "SLIDE_TITLE".
<j:stylesheet version="1.0" width="1024" height="768"
generate-heading-slides="none"
xmlns:j="http://www.dstc.edu.au/ns/2002/JackSVG/stylesheet"
xmlns="http://www.w3.org/2000/svg">
<j:master slide-class="DEFAULT">
<j:block para-class="SLIDE_TITLE"/>
</j:master>
</j:stylesheet>
This produces a slide with the title in the top left corner. This is because without any other attributes the calculated region for the text block is the entire slide (i.e. 800x600) and the text is palaced in the top left corner of it.
The program would have also produced an error about missing font
metrics for "sans-serif". Because no fonts have been specified, it
defaults to using "sans-serif" for which there are no known font
metrics. You can ignore the error by running the command with a
--Force flag (or -F for
short).
To fix the above two problems, a font is specified for the
text. This will be placed in the master
element because we want to use that font throughout the slide. It will
be inherited by all items on the slide unless overridden. Additionaly,
we will set the size of the font, the font weight, and text
alignment. These are set in the block element, because we don't want
them to be inherited by other elements.
The bounds of the region will also be set. We shall specify
absolute sizes using (x, y, width, height). The
debug attribute is set to "red" so we can see the
bounds of the region. This is useful during the design stages.
In this step, we will also add a HEADING fragment to draw a background that will be seen behind every slide. The heading fragment is incorporated into the beginning of the SVG file, once only. Since it is at the beginning, it will be drawn behind everything else that follows.
<j:stylesheet version="1.0" width="800" height="600"
generate-heading-slides="none"
xmlns:j="http://www.dstc.edu.au/ns/2002/JackSVG/stylesheet"
xmlns="http://www.w3.org/2000/svg">
<j:fragment name="HEADER">
<rect x="0" y="0" width="800" height="600" style="fill:#003;"/>
</j:fragment>
<j:master slide-class="DEFAULT"
number-buttons-prev="0"
number-buttons-next="0"
number-buttons-advance="0"
number-buttons-index="0">
font-family="Helvetica">
<j:block para-class="SLIDE_TITLE"
font-size="48" font-weight="bold" text-align="center" fill="white"
x="100" y="30" width="600" height="48" debug="red"/>
</j:master>
</j:stylesheet>
The SVG file generated from this stylesheet should appear as a dark blue background with the slide title appearing inside a red outlined rectangle.
Although we can now see the slide title, we can only see the first slide and there is no way to get to the other slides. We need to add navigation buttons. These are placed in a fragment of SVG named "BUTTONS".
You are free to use any SVG commands to draw the buttons, but you must identify them using special variables. These variables will expand to unique names for each instantiation of the button fragment when it is duplicated on every slide they are used.
To simplify updating how the the buttons are drawn, we define the buttons as a symbol in the HEADER fragment. In drawing the buttons the symbols are used. Some animation effects are added to the buttons so the user knows that they can click on them. The four buttons are to go back to the previous slide, forward to the next slide, up to the index, and advance forward to the next slide.
The advance button and the next button both go to the next slide: the difference is that advance runs any transitional animation effects, while next does not. Usually the user would used the advance button to show the slides in sequence, but might use the next button to jump ahead quickly. A stylesheet does not have to use them both if it doesn't want to. In this case the advance button and the next button function exactly the same, because there are no fade-in or fade-out effects defined yet.
Note also that we have changed the number-buttons-* attributes to have a value of one. If this was not done, an error would occur because the substitution variable corresponding to the button would not be defined.
<j:stylesheet version="1.0" width="800" height="600"
generate-heading-slides="none"
xmlns:j="http://www.dstc.edu.au/ns/2002/JackSVG/stylesheet"
xmlns="http://www.w3.org/2000/svg">
<j:fragment name="HEADER">
<defs>
<!-- Arrow button -->
<symbol id="arrow" viewBox="-50 -50 100 100"
preserveAspectRatio="midXMidY" style="stroke:white;stroke-width:0.25">
<circle cx="0" cy="0" r="15"/>
<line x1="-20" y1="0" x2="30" y2="0"/>
<line x1="0" y1="-20" x2="0" y2="20"/>
<polyline points="20 10 30 0 20 -10" style="fill:none"/>
</symbol>
</defs>
<rect x="0" y="0" width="800" height="600" style="fill:#003;"/>
</j:fragment>
<j:master slide-class="DEFAULT"
number-buttons-prev="1"
number-buttons-next="1"
number-buttons-advance="1"
number-buttons-index="1"
font-family="Helvetica">
<j:block para-class="SLIDE_TITLE"
font-size="48" font-weight="bold" text-align="center" fill="white"
x="100" y="30" width="600" height="48" debug="red"/>
<j:fragment name="BUTTONS">
<use id="{$BUTTON_PREV_ID_1}" xlink:href="#arrow"
width="100" height="100" transform="translate(100,100)rotate(180)"
style="fill:#003;">
<set attributeName="fill" attributeType="CSS" to="#006;"
begin="mouseover" end="mouseout"/>
</use>
<use id="{$BUTTON_NEXT_ID_1}" xlink:href="#arrow"
width="100" height="100" transform="translate(700,00)"
style="fill:#003;">
<set attributeName="fill" attributeType="CSS" to="#006;"
begin="mouseover" end="mouseout"/>
</use>
<use id="{$BUTTON_INDEX_ID_1}" xlink:href="#arrow"
width="100" height="100" transform="translate(0,600)rotate(-90)"
style="fill:#003;">
<set attributeName="fill" attributeType="CSS" to="#006;"
begin="mouseover" end="mouseout"/>
</use>
<use id="{$BUTTON_ADVANCE_ID_1}" xlink:href="#arrow"
width="100" height="100" transform="translate(700,500)"
style="fill:#003;">
<set attributeName="fill" attributeType="CSS" to="#006;"
begin="mouseover" end="mouseout"/>
</use>
</j:fragment>
</j:master>
</j:stylesheet>
You can now navigate between the different slides. The last slide (the one without any title) is the index slide.
With the stylesheet in the previous step, the slides will appear distorted if the viewer is not the same aspect ratio as the slides. The slide scales to fit the window.
To fix this problem, add a
preserveAspectRatio attribute to the stylesheet
element. This attribute is described in detail in the SVG
specification.
Since the design layout of the slide is centred, we will use "xMidYMid". For other layouts, another value might be more appropriate.
<j:stylesheet version="1.0" width="800" height="600" generate-heading-slides="none" preserveAspectRatio="xMidYMid" xmlns:j="http://www.dstc.edu.au/ns/2002/JackSVG/stylesheet" xmlns="http://www.w3.org/2000/svg"> ...
The circles in the buttons will now always be circles, no matter what shape your viewer's window may be.
After adding the preserveAspectRatio
attribute, if the viewer is not the correct size extra then some white
background will be showing. This is not a problem when presenting the
slides in full-screen display mode. However, it doesn't look the best
when not in full-screen mode.
To get around this, make the background region larger than the actual slide. In this particular case, since the slide is always centered, the background needs to be made to extend beyond all four edges.
Change the background rectangle to:
<rect x="-400" y="-300" width="1600" height="1200" style="fill:#003;"/>
The white background will still be shown if the viewer is made very wide or very thin. However, those extreme cases we shouldn't need to worry about.
The main body contents of the slides is not seen because there is no body block specified. Add the following to the stylesheet, inside the master element:
<j:block para-class="BODY"
font-size="32" fill="white"
margin-top="175" margin-left="100" margin-right="100" margin-bottom="50"
debug="red"/>
Note that we are using the relative way of defining the region. Here, the margins are inset from the four edges of the slide.
The body text now appears. However, they aren't formatted or indented properly. The thumbnails on the index slide also appears inside the body block. However, right now they are just plain white rectangles.
The text in the body is all aligned to the left. This is because there is no text block defining how they should be indented. Add the following block to the slide master:
<j:block para-class="POINT.1" margin-left="30"/>
This will indent the first level bullet points. However, you will see that it also indents not just the first level bullet points, but all the levels. This is due to the name lookup rule: when it fails to find a POINT.2 it will try POINT.1; when it fails to find POINT.3 it will try POINT.2 etc.
To add a symbol for the bullets, a fragment needs to be added to the slide master. Add the following fragment to the slide master:
<j:fragment name="BULLET">
<circle cx="15" cy="12" r="5" style="fill:white;"/>
</j:fragment>
Notice again that it is applied to all points at all levels. Again, this is due to the name lookup mechanism. For first level points, it looks for fragments called "BULLET.POINT.1", for second level points it looks for "BULLET.POINT.2" etc. Hence all the points will use "BULLET".
To make subitems look different, add other fragments with different names to the slide master. Add the following:
<j:block para-class="POINT.2" font-size="24" margin-left="50"/>
<j:fragment name="BULLET.POINT.2">
<rect x="35" y="6" width="7" height="7" style="fill:white;"/>
</j:fragment>
Bullets are drawn with the origin at the top left of the bullet point. To indent the bullet and the paragraph contents, increase the left margin of the block as well as drawing the bullet further to the right.
Notice that all lower levels will find these new definition before it finds the level one defined ones. Hence, these new definitions are used for all subsequent levels (level 3, 4, etc).
The thumbnails on the index slide are by default plain white rectangles with a black outline. To change this, define a fragment to draw them.
The thumbnail drawing fragments are instantiated in a coordinate system where the thumbnail is draw at the full slide dimensions. It will be scaled appropriately to appear as a small thumbnail. This is why the dimensions and font sizes are so large.
A simple thumbnail fragment is:
<j:fragment name="THUMBNAIL">
<circle cx="400" cy="300" r="300"
style="fill:#003;stroke:white;stroke-width:20"/>
<text style="text-anchor:middle;font-size:300;fill:white;"
x="400" y="400">{$SLIDE_NUMBER}</text>
</j:fragment>
Note the use of the variable
$SLIDE_NUMBER to get the text of the text label
of the thumbnail.
The problem with the simple thumbnail fragment in the previous step is that the spacing between them is too big. The margins around thumbnails can be controlled by a block called "THUMBNAIL". The margins are applied to the outside of the thumbnail. This block is different to others in that only the margin attributes are used from it. Specifying font properties, and other positioning information will have no effect.
Another problem is that when the mouse cursor goes over the text
it becomes an I-beam for text selection rather than clicking. To fix
this we disable pointer events on the text by setting the
pointer-events attribute to "none".
Some animation is also required to give the user feedback. To do this we usually need to identify and reference elements. Unique names can be generated by concatenating the prefix of "thumb-bg-" to the internal slide number variable $SLIDE_INTERNAL_NUMBER. This approach ensures that every occurance of the element on the different slides will have its own unqique name.
The internal slide number should be used for uniquely identifying items on slides. The internal slide numbers are guaranteed to be always unique to every slide.
Slide numbers aren't enough to indicate which slide the user is
interested in, so we also add a block called "THUMBNAIL_LABEL". The labels
will be created with a property of
"none". To show them when the mouse moves over the thumbnail, a
set is added to the fragment. It uses the
THUMBNAIL_LABEL_ID variable to reference the appropriate label.
<j:fragment name="THUMBNAIL">
<circle cx="400" cy="300" r="300"
style="fill:#003;stroke:white;stroke-width:20">
<set
attributeName="fill" attributeType="CSS" to="#009;"
begin="mouseover" end="mouseout"/>
<set xlink:href="#thumb-text-{$SLIDE_INTERNAL_NUMBER}"
attributeName="fill" attributeType="CSS" to="#ff0;"
begin="mouseover" end="mouseout"/>
<set xlink:href="#{$THUMBNAIL_LABEL_ID}"
attributeName="display" attributeType="CSS" to="inline"
begin="mouseover" end="mouseout"/>
</circle>
<text id="thumb-text-{$SLIDE_INTERNAL_NUMBER}"
style="text-anchor:middle;font-size:300;fill:white;"
pointer-events="none"
x="400" y="400">{$SLIDE_NUMBER}</text>
</j:fragment>
<j:block para-class="THUMBNAIL"
margin-left="0" margin-right="0"
margin-top="100" margin-bottom="100"/>
<j:block para-class="THUMBNAIL_LABEL"
x="200" y="565" width="400" height="24"
font-weight="bold" font-size="24" fill="white" text-align="center"
debug="red"/>
The spacing of the text is too tight. This can be fixed by adding margins to the appropriate blocks.
We add a margin-bottom to the top level point, a margin-top to the second level points, and move the second level bullets down slightly.
You can play around with the margins to get different spacing effects.
<j:block para-class="POINT.1" margin-left="30"
margin-bottom="10"/>
<j:fragment name="BULLET">
<circle cx="15" cy="12" r="5" style="fill:white;"/>
</j:fragment>
<j:block para-class="POINT.2" font-size="24" margin-left="50"
margin-top="7"/>
<j:fragment name="BULLET.POINT.2">
<rect x="35" y="12" width="7" height="7" style="fill:white;"/>
</j:fragment>
To make the slide master more complete, add a text block for the slide's subtitle:
<j:block para-class="SLIDE_SUBTITLE"
font-size="28" font-weight="bold" text-align="center" fill="white"
x="150" y="90" width="500" height="28" debug="red"/>
Also we place some text on the slide to show the slide number. The numbers are obtained from variables, which will be expanded when the fragment is copied out to each slide. Since this is a fragment and not a text block, no font specifications will be inherited so we must explicitly specify the font family as well as the other font parameters.
<j:fragment name="BACKGROUND">
<text x="400" y="580"
style="font-family:Helvetica;font-size:18;font-weight:bold;
text-anchor:middle;fill:#77f">
{$SLIDE_NUMBER} of {$SLIDE_TOTAL}</text>
</j:fragment>
The slide numbers appear on the index slide as "- of -" because the index slide does not have a slide number, and so those variables expand to a single hyphen.
The slide numbers on the index slide are covered by the thumbnail labels. We won't worry about that here, because we will create a different slide master for the index slide later in the tutorial.
Finally, we remove the boxes around the text block regions. Either delete the attributes, or set their values to "none".
You have now created a basic, but usable stylesheet.
The fade-in animation is run when a slide is displayed. You can
use any SVG animation you want (set,
animate, animateTransform,
etc) to create the animations. There are just two special things you
need to do: trigger the animation, and placing the commands.
The fade-in effects should trigger off the variable
$FADE_IN_BEGIN. This event will occur when the
slide is advanced into.
The animation commands are placed into fragments on the slide master. There are a number of options here. In a simple stylesheet, you could put all your animation commands into the ANIMATE_THIS fragment. However, in anticipation of more complex animation effects, we will put the fade-in effects into the "ANIMATE_PREV" fragment.
Add the following fragment to the slide master:
<j:fragment name="ANIMATE_PREV">
<animate xlink:href="#{$BODY_ID}"
attributeName="opacity" attributeType="CSS" from="0" to="1"
begin="{$FADE_IN_BEGIN}" dur="1"/>
</j:fragment>
The variable $BODY_ID will expand to the internal id of the slide body. The fade-out animation increases the opacity of the contents of the slide body from zero to 100% over the one second immediately after the slide is displayed.
You will see the animation when you hit the advance button (lower right). The animation will not be run when you use the next button (upper right).
In this step we will add a corresponding fade-out effect. The fade-out animations are applied when leaving the current slide.
The time event to trigger the animations from is obtained from
the variable $FADE_OUT_BEGIN, and we shall put
the commands in the "ANIMATE_NEXT" fragment:
<j:fragment name="ANIMATE_NEXT">
<animate xlink:href="#{$BODY_ID}"
attributeName="opacity" attributeType="CSS" from="1" to="0"
begin="{$FADE_OUT_BEGIN}" dur="1"/>
</j:fragment>
For the $FADE_OUT_BEGIN variable to be
defined, you will also have to define the
advance-delay attribute to greater than zero.
This attribute is placed in the master element, and indicates how long
to wait after the user has clicked the advance button before the slide
is hidden. The value is in seconds. It should be long enough so that
all your animation effects are finished.
<j:master slide-class="DEFAULT" number-buttons-prev="1" number-buttons-next="1" number-buttons-advance="1" number-buttons-index="1" advance-delay="1" font-family="Helvetica">
You will notice that hitting the advance button on the index slide produces a delay before anything happens. This is because the animation delay is present (even though there is nothing to see). This will get more pronounced later, and the real solution is to create a separate master for the index slide instead of having it use the same master as the normal slides.
This apparent delay is something you should keep in mind when
designing your slides. For example, a bad design would be to have a
fade out effect that spends 1 second animating the title and then 5
seconds animating the subtitle before the slide changes (an
advance-delay of 6 seconds). It might look
great, but no matter how beautiful the subtitle animation looks it is
not going to be seen if a particular slide does not have a subtitle -
there's going to be a huge delay where apparently nothing is
happening. Don't force your users to make subtitles mandatory just to
make your stylesheet work properly.
Let's try something more complicated with the fade-out effect.
Change the ANIMATE_NEXT fragment to be the following:
<j:fragment name="ANIMATE_NEXT">
<animateTransform xlink:href="#{$SLIDE_TITLE_ID}"
attributeName="transform" attributeType="XML"
type="translate" from="0,0" to="0,-100"
begin="{$FADE_OUT_BEGIN}" dur="1"/>
<animate xlink:href="#{$BODY_ID}"
attributeName="opacity" attributeType="CSS" from="1" to="0"
begin="{$FADE_OUT_BEGIN}+1" dur="1"/>
</j:fragment>
The effect we want is that the title moves up out of the way in the first second, and then in the second second the body contents fade away.
If you just made those changes, the only effect you will see is the slide title moving - and the fading is not seen. It does occur. However, since the is set to one second, the slide is hidden after one second has passed and you don't see the fading effect because it occurs after that one second has passed.
Make sure you also change the
advance-delay to two seconds:
<j:master slide-class="DEFAULT" number-buttons-prev="1" number-buttons-next="1" number-buttons-advance="1" number-buttons-index="1" advance-delay="2" font-family="Helvetica">
That works, but we are not quite there yet.
In the previous step, you will see the slide title move away and suddenly appear again during the fading effect. This is because the simple animation command moves the slide title away during the first second, but after the second is over the animation effects are finished and the slide title returns back to its usual place. We want it to stay out of the way.
The wrong way to do this would be to freeze the animation effect. It is wrong because then when we return to the slide (e.g. hit the previous slide button, or run through the slides a second time) the slide title will not be where it should.
One right way to do this is to use an animate command that keeps it out of the way until the entire fade-out animation is finished. The command to use is:
<animateTransform xlink:href="#{$SLIDE_TITLE_ID}"
attributeName="transform" attributeType="XML"
type="translate"
values="0,0; 0,-100; 0,-100" keyTimes="0;0.5;1"
begin="{$FADE_OUT_BEGIN}" dur="2"/>
If you are running the Adobe SVG plug-in, you can see what is really happening by right-clicking on the slide and selecting "Zoom out", and then advance to the next slide.
When you click the advance button on the index slide, you will think nothing is happening. The two second animation is happening, but since there is no slide title you don't see anything happening.
To complete this section, fade-out effects will be added to complement those extra fade-in effects. Additionally, animation effects will be applied to the slide subtitle.
<j:fragment name="ANIMATE_PREV">
<animateTransform xlink:href="#{$SLIDE_TITLE_ID}"
attributeName="transform" attributeType="XML"
type="translate"
values="0,-100; 0,-100; 0,0" keyTimes="0;0.5;1"
begin="{$FADE_IN_BEGIN}" dur="2"/>
<animateTransform xlink:href="#{$SLIDE_SUBTITLE_ID}"
attributeName="transform" attributeType="XML"
type="translate"
values="650,0; 650,0; 0,0" keyTimes="0;0.75;1"
begin="{$FADE_IN_BEGIN}" dur="2"/>
<animate xlink:href="#{$BODY_ID}"
attributeName="opacity" attributeType="CSS" from="0" to="1"
begin="{$FADE_IN_BEGIN}" dur="1"/>
</j:fragment>
<j:fragment name="ANIMATE_NEXT">
<animateTransform xlink:href="#{$SLIDE_TITLE_ID}"
attributeName="transform" attributeType="XML"
type="translate"
values="0,0; 0,-100; 0,-100" keyTimes="0;0.5;1"
begin="{$FADE_OUT_BEGIN}" dur="2"/>
<animateTransform xlink:href="#{$SLIDE_SUBTITLE_ID}"
attributeName="transform" attributeType="XML"
type="translate"
values="0,0; -650,0; -650,0" keyTimes="0;0.25;1"
begin="{$FADE_OUT_BEGIN}" dur="2"/>
<animate xlink:href="#{$BODY_ID}"
attributeName="opacity" attributeType="CSS" from="1" to="0"
begin="{$FADE_OUT_BEGIN}+1" dur="1"/>
</j:fragment>
A word of warning: don't go overboard with animation effects. The transition effects should be subtle, and not distract the audience from the presentation's message and content.
Should mention the importance of declaring metadata in the stylesheet. Especially, it's effect on named items and variable names in the generated SVG, and if your animations depend of them (e.g. subtitle_ID)