Keywords: layout, constraints, expression evaluation
Biography
Cameron is a PhD student at Monash University in Melbourne.
Biography
Kim is a co-director of the Optimisation and Constraint Solving Research Group at Monash University, Melbourne.
Biography
Bernd is senior lecturer at Monash University, Melbourne.
SVG [SVG11] does not support adaptation of the documents layout to the viewing context, such as adapting the layout to take into account the users desire for larger fonts or the size of the browser window. Adding declarative layout mechanisms to SVG would allow such adaptive behaviour. One suggestion is that SVG allow attribute values to be specified with expressions and that these expressions be evaluated at display time to determine the attribute values [MMT02] . Here we describe a minimal extension to SVG 1.1, CSVG, that provides this capability. We demonstrate that this extension allows the author to create documents with sophisticated layout behaviours that otherwise would not be possible without the use of script. We have fully implemented the suggested SVG extensions in Batik. Our implementation demonstrates that this can be achieved with remarkably little code overhead compared to the standard version. Furthermore, we show that our extension combines well with XSLT-definition of custom elements, allowing efficient automatic update of the shadow tree if custom element attributes are changed as a result of user interaction or animation.
1. Introduction
2. Extension to SVG
2.1 Specifying constraints
2.2 Expression evaluation
3. CSVG example
4. Templates
4.1 Templates and constraints
4.2 Template example
5. Implementation
6. Comparison with scripting
7. Future work
8. Conclusion
Footnotes
Bibliography
The desire for declarative layout mechanisms in SVG [SVG11] which allow the layout to adapt to the viewing context has been expressed before [BTM01] and has been explicitly included in previous SVG requirements documents [SVGReq] . One suggestion is that SVG allow attribute values to be specified with expressions and that these expressions be evaluated at display time to determine the attribute values [MMT02] . Here we describe a minimal extension to SVG 1.1, CSVG, that provides this capability. We demonstrate that this extension allows the author to create documents with sophisticated layout behaviours that otherwise would not be possible without the use of script. We have fully implemented the suggested SVG extensions in Batik[1] Our implementation demonstrates that this can be achieved with remarkably little code overhead compared to the standard version.
As a motivating example consider the user interface widget shown in Figure 1 . This has been written in CSVG and demonstrates three kinds of layout adaptation. First, the layout adapts to the size of text elements. Text is unique in SVG in being the only graphical object for which the bounding box cannot be determined before rendering. Unfortunately, as our motivating example illustrates, SVG authors often wish to lay out other graphical objects relative to the dimensions of text. In standard SVG the author must either assume the dimensions of the text are unchanged at display time or use script to get the bounding box at display time. Of course, it is unsafe to assume the text size is fixed as the user agent may use a font different from the one specified in the SVG document. An important instance of this scenario is when documents are viewed by users with poor sight who require large font sizes. Most current SVG documents do not adapt well to such client-side font changes.
Adaptation to text element size is also useful in documents that must adapt to different languages. If a switch element is used to select different text based on the user agent's selected language, the dimensions of the text element will likely be different and the layout must be adapted to the new size. Figure 2 shows how the layout adapts to a larger font size and to a different language.
The second kind of adaptation provided by our example widget is to adjust the layout to the dimensions of the browsing window. Figure 3 demonstrates how the example widget layout adjusts to a narrower browsing window such as that on a PDA. Such adaptation is important if SVG is to allow the same document to be viewable on a wide range of viewing devices rather than requiring the author to generate different documents with essentially the same content for different platforms. In some cases the simple scaling currently afforded by SVG 1.1 is sufficient but this may result in small, illegible text and poor use of the display area.
Another example illustrating the need for adaptation to the browsing window is the linear flow diagram shown in Figure 15 . When displayed on a computer monitor the diagram would be better shown laid out horizontally, as computer monitors typically have a greater width than height. When viewed on a PDA, however, the diagram should be displayed vertically, since a PDA usually has a portrait orientation. If it is being viewed in a resizable window, we want the diagram to adapt to these changes in canvas dimensions as the resizing occurs.
The third kind of layout adaptation provided by our example is layout modification as a result of user interaction. This is shown in Figure 4 , where unchecking the "Use HTTP proxy" checkbox causes a part of the form to be hidden and the remaining widgets to be re-layed out. Adaptation to user interaction is clearly an important requirement for interactive applications and user interfaces. For instance, menus or expanding/collapsing directories in a file system tree or nodes in an organisation chart.
Layout adaptation as a result of animation is closely related to this. Rather than having to explicitly animate every component in the document whose layout changes as a result of the animation one need only animate the principal objects and allow the layout of the secondary objects to be updated automatically. An example of this is given in Figure 20 and Figure 21 . Here the edges of the graph automatically follow the animated nodes without being explicitly animated themselves.
The need to support efficient, incremental re-layout of document elements is, we believe, a powerful reason for providing declarative mechanisms in SVG for specifying layout and style properties. Of course script can be used to modify SVG element attributes when other document elements are modified as a result of user interaction but this requires event handlers to be explicitly added to objects to be notified of some property change and consequent propagation of these changes, in a cascading effect. With a declarative syntax for specifying object properties with expressions, the SVG viewer can determine which expressions depend on a particular property and force the re-evaluation of these dependant expressions. This re-evaluation must be performed regardless of the method of implementation, be it script or constraints, but declarative specification allows the user agent to manage the updates for the author.
The other main extension provided by CSVG is custom elements whose shadow tree is specified declaratively using XSLT (similar to the dropped RCC proposal). One might think that providing such custom elements would remove the need for display-time evaluation of base SVG attribute values since one could use XSLT to compute the values of the attributes of the base SVG elements when generating the shadow tree. In fact even with custom elements it proves very useful to allow base SVG elements to have dynamically evaluated attribute values.
The first reason is that XSLT performs static generation of the shadow tree and so it does not support efficient regeneration of the shadow tree when custom element attributes are modified as a result of user interaction or animation. Since CSVG allows the generated shadow tree elements' attributes to be specified in terms of the custom element attributes, regeneration of the shadow tree is not required in many common cases. Propagation of changes from the custom element to the shadow tree is performed automatically.
A second reason is that XSLT is intended to implement a purely syntactic transformation: it has no way of determining semantic properties, such as the bounding box of text elements. Thus layout adaptation to text size requires either script or the shadow tree elements to have expressions specifying attribute values.
We describe CSVG and the language design decisions and how we have extended Batik to support this extension. Our implementation utilises one-way constraint solving algorithms to propagate changes which occur due to interaction. One-way constraints are efficient to compute, and are used in a variety of applications including GUIs, spreadsheets and customisable graphic editors such as Visio [VHM01] . We demonstrate the usefulness of CSVG by showing how it provides the different types of adaptation discussed above and compare the resulting code with that obtained using script and SVG 1.1. Furthermore, we show that such an extension is straightforward to implement and requires relatively little additional code (our extension to Batik required only 2.5% more code assuming that an XPath implementation is available). Importantly, CSVG's syntax and semantics is designed to be backwards compatible with standard SVG documents and allows default values to be supplied for browsers which do not understand the extension.
The extension we have developed to allow expression-based attributes is reasonably straightforward.
To specify an expression to be used to compute the
value of an attribute or CSS property a c:constraint
element is placed
as the child of the element whose attribute or CSS property is being
constrained. The use of the c:constraint
element parallels the use
and behaviour of the various SMIL elements used to animate attributes and
CSS properties in SVG. An attributeName
attribute indicates which attribute
or CSS property is being constrained and a value attribute specifies the
actual expression to be evaluated. The expression is written in XPath 1.0.
1 <svg xmlns="http://www.w3.org/2000/svg" width="300" height="300" viewBox="0 0 300 300"> 2 <circle id="c" cx="100" cy="100" r="75"/> 3 <rect width="150" height="150"> 4 <c:constraint attributeName="x" value="id('c')/@cx"/> 5 <c:constraint attributeName="y" value="id('c')/@cy"/> 6 </rect> 7 </svg> |
Figure 5: Simple CSVG example (code)
Figure 5 and Figure 6
show the code and resulting image for a simple example
where the x
and y
attributes of a rectangle are specified to be equal to
the centre point of a circle. In both expressions, the core XPath function
id
selects the circle
element. In the first expression, the cx
attribute
of the circle
element is then selected, in the second expression the cy
attribute is chosen. In this example it did not matter that an XPath
implementation would evaluate both of the expressions by getting the string
value of the respective attributes. Complex expressions combining
different values may need to be able to distinguish between different
types of expressions (here, for example, the fact that cx
and cy
are lengths).
1 <svg xmlns="http://www.w3.org/2000/svg" width="300" height="300" viewBox="0 0 300 300"> 2 <rect id="r" x="50" y="50" width="100" height="200" fill="blue"/> 3 <rect y="50" width="100" height="100" fill="red"> 4 <c:constraint attributeName="x" value="id('r')/@x + id('r')/@width"/> 5 </rect> 6 </svg> |
Figure 7: Simple CSVG example adding lengths (code)
To allow XPath expressions to operate on SVG types the XPath implementation in
the CSVG extension is extended to overload operators.
For example, take the code in Figure 7 ,
where a red rectangle is placed to the right of a blue rectangle. In
a standard XPath implementation the expression id('r')/@x + id('r')/@width
would result in the string values of the x
and width
attributes of the first
rectangle being converted to numbers and these numbers then added together.
Since the strings "2in"
and "2cm"
are not valid numbers, the result is NaN.
In our implementation the XPath engine knows the types of each attribute that
can occur on an SVG element and also allows overloading the numeric operators
so that sensible operations can occur on SVG types. Here, the two Length
values are converted to user units and added to result in a new Length
value.
The SVG types that the XPath processor supports are Length, Point, Rect
and Matrix. The standard XPath operators are overloaded for certain combinations
of these values to perform useful operations. For example, a Rect can be
multiplied by a standard XPath number to result in a Rect whose x
, y
, width
and
height
values have been multiplied by that number.
The availability of operators on matrices allows us to work with transforms
directly, for example, a transformation can be applied by multiplying
a Point with the current transformation matrix. Any
combination of operands and operator that does not have a special behaviour
in CSVG will be evaluated by falling back to the standard XPath operator.
A table of all of the available operator overloadings can be seen on the project website.
A CSVG implementation will know the type of each standard attribute of the
SVG elements, which is why in the previous example referring to a rect
element's x
attribute gets a Length value.
The special operators are required for the remaining types in SVG. However, they are still used in checking expressions for type errors. For example, assume that the font-size attribute of an element is constrained to be equal to the fill attribute of another element. A CSVG implementation will flag this as an error. If for some reason an attribute manipulation is required that cannot be implemented in terms of the given operators, string functions can still be used to represent the representation of these attributes.
The special operators are required for the remaining types in SVG. However, they are still used in checking expressions for type errors. For example, assume that the font-size attribute of an element is constrained to be equal to the fill attribute of another element. A CSVG implementation will flag this as an error. If for some reason an attribute manipulation is required that cannot be implemented in terms of the given operators, string functions can still be used to represent the representation of these attributes.
There are also a number of XPath extension functions which are defined in
CSVG. Table 1 lists these functions. In addition to
extension functions which perform some calculation that is possible with the
DOM — such as finding the bounding box of an element, or its transformation
matrix — there are those which create SVG values and manipulate them.
For example, to add 2cm to some element's x
attribute
you could use the expression id('some')/@x + c:Length('2cm')
. This uses
the c:Length
constructor function to create a Length value from the string
"2cm". Since some lengths depend on a context (such as the current viewport
for perctentage values, and the current font size for em and ex values), the
length constructor functions take a node as an optional second argument to be
used as the context.
Because of the operator overloading and handling of types, the XPath engine is extended in some backwards incompatible ways. Obviously, this would be a concern for implementors, since leverage of existing code is important. In cases where an existing XPath implementation is not readily extended to support the expressions possible in CSVG, these expressions could be thought of as just a shorthand for the traditional XPath syntax. Both the typed attribute selection and operator overloading could instead be performed only with extension functions. XPath 1.0 does allow extension functions and for such functions to return values of non-standard types. CSVG expressions written in this form would be much less readable and less intuitive to write, but they would be equivalent. A CSVG user agent could preprocess CSVG expressions in to a form that is understood by a standard XPath engine.
Function name | Description |
---|---|
c:if | Returns one value if a given condition is true, another value if it is false. |
c:min | Returns the smallest number passed to the function. |
c:max | Returns the largest number passed to the function. |
c:bbox | Returns the bounding box of an object. |
c:screenCTM | Returns the screen current transformation matrix of an object. |
c:CTM | Returns the current transformation matrix of an object. |
c:height | Returns the height property of a Rect. |
c:width | Returns the width property of a Rect. |
c:x | Returns the x property of a Point or Rect. |
c:y | Returns the y property of a Point or Rect. |
c:instance | When used inside a shadow tree, returns the custom element which instantiated the shadow tree. |
c:inverse | Inverts a Matrix. |
c:property | Gets a property from a custom element. |
c:time | Returns the number of seconds since the document was loaded. |
c:viewport | Returns the viewport property of the root SVG element. |
c:Length | Constructor function to create a Length. |
c:LengthH | Constructor function to create a horizontal Length. |
c:LengthV | Constructor function to create a vertical Length. |
c:Matrix | Constructor function to create a Matrix. |
c:Point | Constructor function to create a Point. |
Table 1
Figure 9 shows an example that uses the c:bbox
extension function to get the bounding box of a text
element.
The c:bbox
function returns a Rect, whose four properties are
extracted using the c:x
, c:y
, c:width
and c:height
functions. These values are used to position
a rectangle to fit around the text. In Figure 10 ,
the user agent's default font family and size are used. Figure 11
shows the same example when a user stylesheet has been applied.
1 <svg xmlns="http://www.w3.org/2000/svg" 2 xmlns:c="http://mcc.id.au/2004/csvg" 3 width="600" height="100"> 4 <text id="t" x="300" y="50" text-anchor="middle"> 5 The rect should contain this text 6 </text> 7 <rect width="0" height="0" stroke="red" stroke-width="2" fill="none"> 8 <c:constraint attributeName="x" value="c:x(c:bbox(id('t'))) - 4"/> 9 <c:constraint attributeName="y" value="c:y(c:bbox(id('t'))) - 4"/> 10 <c:constraint attributeName="width" value="c:width(c:bbox(id('t'))) + 8"/> 11 <c:constraint attributeName="height" value="c:height(c:bbox(id('t'))) + 8"/> 12 </rect> 13 </svg> |
Figure 9: Simple CSVG example adapting to text size (code)
SMIL defines its animations in terms of a function f(t) which maps times to
values. CSVG has a function c:time
which returns the number of seconds
since the document was loaded. This can be used to perform any sort of
animation by writing an expression in terms of this time function.
Any XPath expression can be used in a c:constraint element so long as it does not cause a cyclic dependency. That is, an expression defining the value for a particular attribute cannot directly or indirectly, via another constraint, refer to itself. Figure 12 shows an example which is invalid due to a cyclic dependency.
1 <svg xmlns="http://www.w3.org/2000/svg" width="300" height="300"> 2 <text id="t1"> 3 <c:constraint attributeName="y" value="id('t2')/@y + 10"/> 4 Some text 5 </text> 6 <text id="t2"> 7 <c:constraint attributeName="y" value="id('t1')/@y"/> 8 More text 9 </text> 10 </svg> |
Figure 12: Cyclic dependency (code)
Variables can be defined with the c:variable
element. Such
elements have a name
attribute, giving the name of the
variable, and a value
attribute gives its expression. This
allows the author to break up complex expressions for reuse.
Variables can be referenced in XPath expressions using
the dollar character followed by the variable name, as in
XSLT. The scoping rules for variables in CSVG are also
similar to those in XSLT. When a variable is referenced,
the corresponding declaration will be searched for in all
enclosing contexts of the expression in the inverse order of
depth and within a single level in reverse document order. The
first c:variable
element with a matching name that is found is
the variable that the variable reference is resolved to. Thus
variables can be redefined by redeclaring them in a position
such that they shadow an existing variable with the same name.
Variable references from that point in the document onwards
will see this new variable with the same name.
In some cases it may be necessary to refer to a variable which occurs later
in the document. For this, a location path can be used to select the relevant
c:variable
element and get its value
attribute.
As mentioned above, one of the primary benefits of using a declarative model for the specification of expressions for attribute value computation is that the user agent can take away from the author the burden of determining when expression re-evaluation should occur. The constraint system that is being used in CSVG is a system of one-way constraints. These constraints, also known as dataflow constraints, are similar to the systems of expressions one can use in spreadsheets, where a cell can be assigned a value from an arbitrary expression, so long as there is no cyclic dependency.
To ensure that expressions are not re-evaluated needlessly, a CSVG user agent must determine the dependencies for the document. The dependency analysis will associate with each expression a set of forward and reverse dependencies. A forward dependency is something that the current expression depends upon. Each reverse dependency is an expression which depends upon the current expression. Currently, there are seven types of dependencies:
c:variable
element with the same name must be watched for changes in its value.
[2]
c:bbox
function call on that element. Bounding boxes
must be handled differently, as many properties (but not all) will cause
an object's dimensions to change if they are modified.
c:viewport
function to get the current dimensions of the viewport. When an
SVGResize event occurs, expressions with viewport dependencies will
be updated.
c:screenCTM
function to get the screen
current transformation matrix for an element then a zoom and pan
dependency will exist for this expression, since zooming and panning
in the user agent (SVGScroll and SVGZoom events) will cause the screen
CTM to change.
c:time
function
to get the current animation time. Such expressions will be updated
each time the animation engine causes a clock tick.
It is this last kind of dependency that is the most difficult to analyse. Since a location path can pass over a number nodes in the document as it is evaluated, modifications to any of these nodes may require the expression to be re-evaluated. We have devised an algorithm for determining the node dependencies for a given XPath expression. It involves partially evaluating the XPath expression by following the steps in any path which is not inside a predicate. At each step as the path is being partially evaluated node dependencies are added for any node which matches the node test of the step.
1 <svg xmlns="http://www.w3.org/2000/svg" 2 xmlns:svg="http://www.w3.org/2000/svg" 3 xmlns:c="http://mcc.id.au/2004/csvg" 4 width="400" height="400"> 5 6 <g id="g" fill="red"> 7 <svg height="350"> 8 <circle cx="100" cy="100" r="10"/> 9 <circle cx="200" cy="200" r="20"/> 10 <circle cx="300" cy="300" r="30"/> 11 </svg> 12 </g> 13 14 <text x="0" y="370" text-anchor="middle" font-size="20"> 15 This is 16 <c:constraint attributeName="x" value="id('g')/*/svg:circle[@r > 25]/@cx"/> 17 </text> 18 <text x="0" y="395" text-anchor="middle" font-size="20"> 19 a big circle 20 <c:constraint attributeName="x" value="preceding-sibling::svg:text/@x"/> 21 </text> 22 </svg> |
Figure 13: Dependency analysis example (code)
To illustrate dependency analysis, consider the example in Figure 13 .
The constraint on line 16 will get the cx
attribute of the
third circle
element in the document, but there are more dependencies
that will be set up than just the one on that attribute. Beginning
at the first step of the location path, the element with ID "g" is
selected. Once that node is selected, all child elements are selected.
In this case there is only one element, an svg
, but if another element
is inserted as the child of the g
element (or one is removed) the
expression will need to be re-evaluated. So, we add a node dependency
to the expression on the g
element for any node that matches the XPath
node test *
. From the svg
element we select any circle
element whose
r
attribute is greater than 25 user units.
For the purpose of dependency analysis, predicates on attribute values in the XPath
expression must be inspected to ensure modifications which would cause the
predicate to evaluate differently cause the whole expression to be evaluated.
However, when following to the next step of the location path, we ignore the predicate
and simply select all of the circle
elements that are children of
the svg
element.
Thus in this case
a node dependency is added to the expression on the svg
element
for all children which match the node test svg:circle
. Then,
attribute dependencies are added for each of the circle
elements'
r
attributes, as a change to an r
may cause a
different circle
to be selected. Finally,
an attribute dependency is added on the circle
elements for the
cx
attribute. Note that even though initially only one
circle
element is selected according to the predicate,
watching all cx
attributes of the circle
elements
is safe and will ensure that constraints are propagated correctly.
When the values of any expression change while the document is being displayed, the change can quickly be propagated by transitively following the dependencies that have been set up. As there are no cycles in the dependency graph, the propagation is guaranteed to terminate after time linear in the number of expressions.
When the expression that determines an attribute or CSS property's value is updated the effect is just as if a SMIL animation had caused the update. That is, for attributes the animated value of the appropriate attribute of the DOM object is changed. For CSS properties, the override stylesheet is modified to include the new value for the property being updated.
To demonstrate our extension to SVG we describe a simple example and how a CSVG user agent would handle it. The example is that of a flow diagram with three nodes. Each node is a rectangle with text centered within and arrows are placed between adjacent nodes. We wish this flow diagram to be viewable both on a computer monitor, which has a landscape orientation (its width is greater than its height) and also on a PDA, which has a portrait orientation (height greater than width). To this end, we want the flow diagram to be laid out different depending on the available space. If there is sufficient horizontal space to lay the flow diagram out horizontally then it should be done so. Otherwise, it should be displayed vertically. We also want each node in the diagram to be the same size, just large enough to hold the largest node label.
1 <svg width="100" height="600" overflow="visible" 2 xmlns="http://www.w3.org/2000/svg" 3 xmlns:c="http://mcc.id.au/2004/csvg"> 4 5 <defs> 6 <marker id="Triangle" viewBox="0 0 10 10" refX="10" refY="5" 7 markerUnits="strokeWidth" markerWidth="16" markerHeight="12" 8 orient="auto"> 9 <path d="M0,0 L10,5 L0,10 z"/> 10 </marker> 11 </defs> 12 13 <!-- horizontal: whether the layout is horizontal or vertical --> 14 <c:variable name="horizontal" 15 value="c:width(c:viewport()) >= $boxWidth * 3 + $desiredGap * 2 16 + $margin * 2"/> 17 18 <!-- minGap: the gap below which the layout will change --> 19 <c:variable name="desiredGap" value="30"/> 20 21 <!-- margin: the gap between the canvas border and the boxes --> 22 <c:variable name="margin" value="10"/> 23 24 <!-- boxPadding: the padding between text and a box border --> 25 <c:variable name="boxPadding" value="10"/> 26 27 <!-- boxWidth: the width of the boxes --> 28 <c:variable name="boxWidth" value="c:max($t1w, $t2w, $t3w) + $boxPadding * 2"/> 29 30 <!-- boxHeight: the height of the boxes --> 31 <c:variable name="boxHeight" value="c:max($t1h, $t2h, $t3h) + $boxPadding * 2"/> 32 33 <!-- box coordinates --> 34 <c:variable name="b1x" value="c:if($horizontal, $margin, 35 c:width(c:viewport()) div 2 - $boxWidth div 2)"/> 36 <c:variable name="b1y" value="c:if($horizontal, 37 c:height(c:viewport()) div 2 - $boxHeight div 2, $margin)"/> 38 39 <c:variable name="b2x" value="c:width(c:viewport()) div 2 - $boxWidth div 2"/> 40 <c:variable name="b2y" value="c:height(c:viewport()) div 2 - $boxHeight div 2"/> 41 42 <c:variable name="b3x" value="c:if($horizontal, 43 c:width(c:viewport()) - $margin - $boxWidth, 44 c:width(c:viewport()) div 2 - $boxWidth div 2)"/> 45 <c:variable name="b3y" value="c:if($horizontal, 46 c:height(c:viewport()) div 2 - $boxHeight div 2, 47 c:height(c:viewport()) - $margin - $boxHeight)"/> 48 49 <!-- text coordinates --> 50 <c:variable name="t1x" value="$b1x + $boxWidth div 2"/> 51 <c:variable name="t1y" value="$b1y + $boxHeight - $boxPadding"/> 52 53 <c:variable name="t2x" value="$b2x + $boxWidth div 2"/> 54 <c:variable name="t2y" value="$b2y + $boxHeight - $boxPadding"/> 55 56 <c:variable name="t3x" value="$b3x + $boxWidth div 2"/> 57 <c:variable name="t3y" value="$b3y + $boxHeight - $boxPadding"/> 58 59 <!-- text dimensions --> 60 <c:variable name="t1w" value="c:width(c:bbox(id('t1')))"/> 61 <c:variable name="t1h" value="c:height(c:bbox(id('t1')))"/> 62 <c:variable name="t2w" value="c:width(c:bbox(id('t2')))"/> 63 <c:variable name="t2h" value="c:height(c:bbox(id('t2')))"/> 64 <c:variable name="t3w" value="c:width(c:bbox(id('t3')))"/> 65 <c:variable name="t3h" value="c:height(c:bbox(id('t3')))"/> 66 67 <!-- boxes --> 68 <g fill="yellow" stroke="black" stroke-width="3"> 69 <rect id="b1" x="10" y="10" width="80" height="40"> 70 <c:constraint attributeName="x" value="$b1x"/> 71 <c:constraint attributeName="y" value="$b1y"/> 72 <c:constraint attributeName="width" value="$boxWidth"/> 73 <c:constraint attributeName="height" value="$boxHeight"/> 74 </rect> 75 <rect id="b2" x="10" y="280" width="80" height="40"> 76 <c:constraint attributeName="x" value="$b2x"/> 77 <c:constraint attributeName="y" value="$b2y"/> 78 <c:constraint attributeName="width" value="$boxWidth"/> 79 <c:constraint attributeName="height" value="$boxHeight"/> 80 </rect> 81 <rect id="b3" x="10" y="550" width="80" height="40"> 82 <c:constraint attributeName="x" value="$b3x"/> 83 <c:constraint attributeName="y" value="$b3y"/> 84 <c:constraint attributeName="width" value="$boxWidth"/> 85 <c:constraint attributeName="height" value="$boxHeight"/> 86 </rect> 87 </g> 88 89 <!-- texts --> 90 <g font-size="20" text-anchor="middle"> 91 <text id="t1" x="50" y="40"> 92 <c:constraint attributeName="x" value="$t1x"/> 93 <c:constraint attributeName="y" value="$t1y"/> 94 XML file 95 </text> 96 <text id="t2" x="50" y="310"> 97 <c:constraint attributeName="x" value="$t2x"/> 98 <c:constraint attributeName="y" value="$t2y"/> 99 XSLT 100 </text> 101 <text id="t3" x="50" y="580"> 102 <c:constraint attributeName="x" value="$t3x"/> 103 <c:constraint attributeName="y" value="$t3y"/> 104 SVG file 105 </text> 106 </g> 107 108 <!-- arrows --> 109 <g stroke="black" stroke-width="1" marker-end="url(#Triangle)"> 110 <line x1="50" y1="50" x2="50" y2="280"> 111 <c:constraint attributeName="x1" value="$b1x + $boxWidth div c:if($horizontal, 1, 2)"/> 112 <c:constraint attributeName="y1" value="$b1y + $boxHeight div c:if($horizontal, 2, 1)"/> 113 <c:constraint attributeName="x2" value="$b2x + c:if($horizontal, 0, $boxWidth div 2)"/> 114 <c:constraint attributeName="y2" value="$b2y + c:if($horizontal, $boxHeight div 2, 0)"/> 115 </line> 116 <line x1="50" y1="320" x2="50" y2="550"> 117 <c:constraint attributeName="x1" value="$b2x + $boxWidth div c:if($horizontal, 1, 2)"/> 118 <c:constraint attributeName="y1" value="$b2y + $boxHeight div c:if($horizontal, 2, 1)"/> 119 <c:constraint attributeName="x2" value="$b3x + c:if($horizontal, 0, $boxWidth div 2)"/> 120 <c:constraint attributeName="y2" value="$b3y + c:if($horizontal, $boxHeight div 2, 0)"/> 121 </line> 122 </g> 123 </svg> |
Figure 14: Flow diagram (code)
Figure 14 shows the code for this flow diagram. Most of the layout logic
has been separated out into c:variable
elements. The first variable,
$horizontal
, determines whether the layout of the diagram will be horizontal
or not. It will be true if the width of the viewport is greater than or
equal to the space to fit three boxes, some nominal gap between these boxes
and also some margin space between the edge of the viewport and the boxes.
Since this variable uses the c:viewport
function it has a viewport dependency.
If the browser window is resized then this expression will be re-evaluated.
The $boxWidth
and $boxHeight
variables calculate the dimensions of the rect
elements that form the nodes in the flow diagram. Since we want all the
rects to the be same size, and all large enough to fit their text, the c:max
function is used to determine the maximum of the text elements' widths and
heights (plus some small amount of padding). These widths are computed in
the variables $t1w
, $t2w
and $t3w
,
while the heights are in $t1h
, $t2h
and
$t3h
. Those text dimension variables use the c:bbox
function to get the
bounding box of each of the text elements.
The box coordinate variables are used to determine the actual positions of
the rect elements in the diagram. In this layout the position of the
middle rectangle will be the same whether the horizontal or vertical layout
is used, since it will be in the center of the viewport. Thus, $b2x
is simply the average of the width of the viewport and the box width, and $b2y
is the average of the viewport height and box height. The coordinates for
the first and thirds rects must be positioned differently depending on the
value of $horizontal
, though. In our example, if we are using the horizontal
layout then the first box should be position at the middle-left of the viewport
and in a vertical layout, it should be at the top-middle. So we use the
c:if
function to choose the correct coordinates based on $horizontal
. The same
applies to $b3x
and $b3y
for the third box, except that it is placed at the
middle-right for horizontal layout and bottom-middle for vertical.
The text positioning variables ($t1x
, $t1y
, $t2x
, $t2y
, $t3x
and $t3y
) simply
compute the center of each box.
Following the c:variable
elements are the actual SVG elements for the flow
diagram. Each of rect elements has four c:constraint
child elements to
set the expression to be used for the x
, y
, width
and height
attributes.
The value attributes of these c:constraint
elements just refer to the variables
that have the computed position and dimension values. The text elements
have similar c:constraint
elements as children for the x
and y
attributes.
Because the user agent will have analysed the expressions in the document
for dependencies it will know which expressions to re-evaluate if the user
resizes the browser window. Initially, the window resize will cause all
expressions with viewport dependencies to be updated. In this example,
the $horizontal
variable and the box coordinate variables all contain a
call to the c:viewport
function and so will be marked dirty and re-evaluated.
Any constraint which depends upon these variables will also need to be updated,
so the user agent will follow the reverse dependency list for each of these
variables once they have been updated. The expressions that depend on the
$horizontal
variable include four of the box coordinate variables (which
have updated already), and the constraints on the coordinate attributes of the
two line elements at the end of the file. Those that depend on the box
coordinate variables are the text coordinate variables and the constraints
assigning the box coordinate variables to the attributes of the rect elements
themselves. This process continues until the user agent has re-evaluated
all of the expressions found as if by computing the transitive closure
of the reverse dependencies.
In this example it will result all of the
constraints on the SVG elements and the following variables being updated:
$horizontal
, $b1x
, $b1y
, $b2x
, $b2y
, $b3x
, $b3y
, $t1x
, $t2y
, $t3x
and $t3y
.
Other expressions, such as those computing the bounding boxes of the text
elements, will not be re-evaluated since their values will not have changed.
In CSVG one can use templates to define the content that is to be rendered in place of the custom element. The RCC-XSLT syntax proposed in a SVG 1.2 working draft is used. [SVG12] [3]
There are a couple of reasons why XSL templates should be used as the prototyping mechanism for CSVG. Firstly, it is a widely used language for specifying transformations from one XML language to another. Rendering custom content is exactly this problem. Many simple transformations are not as easily done when using DOM calls in script.
The issue of shadow tree regeneration is an important one. With script based RCC, the author must explicitly control when and how the shadow tree is to be updated. This will mostly be by attaching DOM mutation event handlers to the custom element. Because the initial shadow tree generation is done using script it is difficult for the user agent to automatically determine the minimum changes necessary. When XSLT and constraints are used, the constraints can be set up to depend on the custom element's attributes, so that changes to the custom element will automatically result in an update in the shadow tree. Notice that efficient incremental update of shadow tree elements properties does not require incremental XSLT processing [VL02] , rather it is achieved by generating elements whose attrbutes are constrained to be functions of the custom element attributes.
Supporting shadow tree elements having constraints on custom elements also allows CSVG to provide animation on custom elements, something which cannot be done with current RCC, since SVG has no support for capturing events when changes caused by animation occur. In CSVG elements in the shadow tree could have attributes which depend on animated attributes of the custom elements, resulting in much more flexibility in animation than is possible in the RCC proposal.
A major hindrance to including XSLT as a mechanism for RCC is that it has no knowledge of the document beyond that of the plain XML. Anything that requires the use of SVG types, such as adding two lengths together or determining a bounding box, cannot be done in plain XSLT. By allowing XSLT to generate constraints we bypass this problem by having these expressions in the shadow tree perform the SVG-dependent computations.
The syntax for using XSLT to generate shadow trees is currently just as a previous SVG 1.2 working draft had suggested. To allow constraints to work between shadow trees and the main document tree, two extra XPath extension functions and one CSVG element are used.
The c:instance
function returns the custom element that the shadow tree
containing the expression is associated with. This makes it possible to
jump back to the original document tree to, for example, select attributes
from the custom element. Constraints will then be set up between the custom
element's attributes and variables or attributes in the shadow tree so that
if the attributes on the custom element are changed — by DOM mutation,
constraint propagation or by animation — the changes will be propagated to
the shadow tree.
Also available for use are the c:property
element and the c:property
function.
The c:property
element, when used inside a shadow tree, defines a property
for the custom element. This c:property
element is used just like a
c:variable
element; it has a name
and a value
attribute. Properties of
custom elements though are accessible from outside the shadow tree by the
c:property
XPath extension function. The function takes as its two arguments
a nodeset containing a custom element and the name of a property defined in
that custom element's shadow tree. This allows information to be made visible
from the implementation of a custom element in a controlled fasion and used in
other constraints in the document. It is effectively an ouptut argument
for the custom element. Only one c:property
element with a given
name is allowed in a shadow tree.
To allow text strings in a shadow tree to be automatically updated via constraints
a c:tval
element is available. This element works similarly to the
standard tref
element in that it inserts some text into the containing
text
element. Instead of having an xlink:href
attribute to
refer to a previously defined text string, the c:tval
element has a
value
attribute which contains an XPath expression which will be evaluated
to determine the text string to be used. Just like other constraint elements,
if any dependency of this expression is updated, the expression will be re-evaluated
to obtain the new string value.
Figure 18 shows an example of a simple graph, consisting of three nodes with arcs between them and labels on both the nodes and the arcs. We want the graph to be defined using templates and that the positions of the nodes be animatable.
The code for this example is shown in Figure 19 . At the end of the listing
is the ex:graph
element which defines the whole graph. Each node in the graph
has a name
, cx
, cy
and label
attribute. One of the nodes also has a final
attribute which causes the node to be rendered with two outer
circles. The ex:arc
elements, defining the lines between the nodes, have
a from
and to
attribute, to select between which nodes the arcs are to be
drawn, and a label
attribute to define the text label for the arc.
1 <svg xmlns="http://www.w3.org/2000/svg" 2 xmlns:xlink="http://www.w3.org/1999/xlink" 3 xmlns:ex="http://mcc.id.au/2004/example" 4 xmlns:c="http://mcc.id.au/2004/csvg" 5 width="400" height="400" overflow="visible"> 6 7 <extensionDefs namespace="http://mcc.id.au/2004/example"> 8 <xsl:stylesheet id="exampleXSL" version="1.1" 9 xmlns="http://www.w3.org/2000/svg" 10 xmlns:svg="http://www.w3.org/2000/svg" 11 xmlns:ex="http://mcc.id.au/2004/example" 12 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 13 14 <xsl:template match="ex:graph"> 15 <xsl:copy-of select="ex:arc"/> 16 <xsl:copy-of select="ex:node"/> 17 </xsl:template> 18 19 <xsl:template match="ex:node"> 20 <c:variable name="cx" value="c:Length(c:instance()/@cx)"/> 21 <c:variable name="cy" value="c:Length(c:instance()/@cy)"/> 22 <c:variable name="final" value="string(c:instance()/@final) = 'true'"/> 23 <c:variable name="b" value="c:bbox(following-sibling::svg:text)"/> 24 25 <circle r="0" fill="white" stroke-width="2" stroke="black"> 26 <c:constraint attributeName="cx" value="$cx"/> 27 <c:constraint attributeName="cy" value="$cy"/> 28 <c:constraint attributeName="r" value="c:max(c:width($b) div 2 + 10, 30) + 5"/> 29 <c:constraint attributeName="display" value="c:if($final, 'inline', 'none')"/> 30 </circle> 31 <circle r="0" fill="#eee" stroke-width="2" stroke="black"> 32 <c:constraint attributeName="cx" value="$cx"/> 33 <c:constraint attributeName="cy" value="$cy"/> 34 <c:constraint attributeName="r" value="c:max(c:width($b) div 2 + 10, 30)"/> 35 </circle> 36 <text text-anchor="middle"> 37 <c:constraint attributeName="x" value="$cx"/> 38 <c:constraint attributeName="y" value="$cy + c:height($b) div 2"/> 39 <xsl:value-of select="@label"/> 40 </text> 41 </xsl:template> 42 43 <xsl:template match="ex:arc"> 44 <c:variable name="from" value="c:instance()/@from"/> 45 <c:variable name="to" value="c:instance()/@to"/> 46 <c:variable name="x1" value="c:Length(c:instance()/../ex:node[@name=$from]/@cx)"/> 47 <c:variable name="y1" value="c:Length(c:instance()/../ex:node[@name=$from]/@cy)"/> 48 <c:variable name="x2" value="c:Length(c:instance()/../ex:node[@name=$to]/@cx)"/> 49 <c:variable name="y2" value="c:Length(c:instance()/../ex:node[@name=$to]/@cy)"/> 50 <line x1="0" y1="0" x2="0" y2="0" stroke="black" stroke-width="1"> 51 <c:constraint attributeName="x1" value="$x1"/> 52 <c:constraint attributeName="y1" value="$y1"/> 53 <c:constraint attributeName="x2" value="$x2"/> 54 <c:constraint attributeName="y2" value="$y2"/> 55 </line> 56 <c:variable name="midx" value="($x1 + $x2) div 2"/> 57 <c:variable name="midy" value="($y1 + $y2) div 2"/> 58 <c:variable name="b" value="c:bbox(following-sibling::svg:text)"/> 59 <rect width="0" height="0" fill="white"> 60 <c:constraint attributeName="x" value="c:x($b) - 1"/> 61 <c:constraint attributeName="y" value="c:y($b) - 1"/> 62 <c:constraint attributeName="width" value="c:width($b) + 2"/> 63 <c:constraint attributeName="height" value="c:height($b) + 2"/> 64 </rect> 65 <text text-anchor="middle"> 66 <xsl:value-of select="@label"/> 67 <c:constraint attributeName="x" value="$midx"/> 68 <c:constraint attributeName="y" value="$midy"/> 69 </text> 70 </xsl:template> 71 </xsl:stylesheet> 72 73 <elementDef name="graph"> 74 <transformer xlink:href="#exampleXSL" type="text/xsl"/> 75 </elementDef> 76 77 <elementDef name="node"> 78 <transformer xlink:href="#exampleXSL" type="text/xsl"/> 79 </elementDef> 80 81 <elementDef name="arc"> 82 <transformer xlink:href="#exampleXSL" type="text/xsl"/> 83 </elementDef> 84 </extensionDefs> 85 86 <g font-size="24" font-family="serif"> 87 <ex:graph> 88 <ex:node name="n1" cx="70" cy="200" label="Start"/> 89 <ex:node name="n2" cx="200" cy="330" label="1"/> 90 <ex:node name="n3" cx="300" cy="100" label="Final" final="true"/> 91 <ex:arc from="n1" to="n2" label="a*b*"/> 92 <ex:arc from="n2" to="n3" label="Λ + c"/> 93 </ex:graph> 94 </g> 95 </svg> |
Figure 19: Graph defined with templates (code)
The template definition for the ex:graph
element from line 87 is just a container for
the ex:node
and ex:arc
elements. It makes sure that the arcs are rendered
before the nodes, so that the lines do not appear over the top of the circles.
The XSLT used to generate the shadow tree for the ex:node
elements is
reasonably simple. First, it defines a few CSVG variables to hold
information given in the ex:node
's attributes. These variables ($cx
, $cy
and
$final
) all use the c:instance
function to select the ex:node
element which
instantiated the current shadow tree. Defined also is $b
which
holds the bounding box of the node's label. If the label would extend
past the edge of the default 30 unit radius circle, the size of the circle is
increased. This is done using the c:max
function in the r
attribute of
the two circles in the template.
The first circle is an outer ring for those nodes which are marked as
"final". It has a constraint on the display
attribute which will prevent
it from beind rendered if the node is not a final node. Finally there is
the text element into which is copied the label specified on the ex:node
element. The label is positioned at the center of the circle, using the
width of the bounding box of that text element (c:height($b)
) to center it
vertically and a simple text-anchor="middle"
attribute for the horizontal
centering.
The template for the ex:arc
element is similar. Note that the $x1
, $y1
,
$x2
and $y2
variables (defining the start and end coordinates of the
arc) use the c:instance
function to access the custom element which
instantiated this shadow tree. XPath is then used to select the sibling
ex:node
element according to the from
and to
attributes so the start and end
coordinates can be found.
After the CSVG variable definitions the line element which is the arc is copied to the shadow tree. Finally, a text element is used to display the arc label. A white rect is drawn just before this to make sure the label is legible over the line.
Now, if any script modifies the cx
and cy
attributes of the ex:node
elements
the positions of the circle, text, line and rect elements which make up the
nodes and arcs will reposition themselves accordingly, since these shadow
tree elements have dependencies on the custom elements in the main document
tree.
We can easily animate a node in the graph by defining an animation on one of
the custom elements. Figure 20 shows the graph example
with the second node animated to move up the canvas. The arcs follow since
in the shadow tree the line endpoints are constrained to be equal to the
centers of the two nodes it links. The code shows a commented out SMIL
animation and a c:constraint
element, both of which would animate
the node. Since our implementation is based on Batik, which currently does
not support SMIL animation, the SMIL animation syntax is not yet supported
(although we plan to do this soon).
If it was supported, the constraints would propagate correctly
to animate the entire node and the arcs would follow. The c:constraint
element achieves the same effect as the SMIL animation by using the c:time
function.
1 <ex:graph> 2 <ex:node name="n1" cx="70" cy="200" label="Start"/> 3 <ex:node name="n2" cx="200" cy="330" label="1"> 4 <!-- animate attributeName="cy" from="330" to="0" begin="0s" end="10s"/ --> 5 <c:constraint attributeName="cy" value="330 * (1 - c:time() div 10)"/> 6 </ex:node> 7 <ex:node name="n3" cx="300" cy="100" label="Final" final="true"/> 8 <ex:arc from="n1" to="n2" label="a*b*"/> 9 <ex:arc from="n2" to="n3" label="Λ + c"/> 10 </ex:graph> |
Figure 20: Graph with node animated (code)
We have implemented the constraint extensions discussed here as an extension to Batik, a Java SVG browser. The complete implementation can be found at the project's web site.
Batik, aside from animation, provided a quite complete implementation of SVG to build upon. For the XPath expression evaluation support, the XPath package from the Xalan project has been used.
The current implementation is not yet as fast as rendering complex CSVG documents without a noticeable delay would require. Profiling data suggests that a large amount of the computation time is spent in the XPath evaluation, which potentially slows down the evaluation additionally by leaving larger amounts of garbage-collectable objects behind. This suggests optimising the XPath implementation within CSVG could make the browser significantly faster. We are planning to investigate this by (partially) compiling XPath expressions.
Batik is a medium sized project. The Batik CVS sources have 295,795
lines of code in total (counting the files in the xml-batik/sources/org
directory) at the time the extension was developed. The addition of
constraint handling needed just an additional 7,362 lines of code. This is
only a 2.5% increase in code. Xalan's XPath implementation, though, takes
a fair amount of code, coming in at around 46,000 lines of code (not counting
localisation files). Taking this into account, the amount of code added to the
base Batik distribution is around 18%; still not unreasonable. An XPath
engine will be needed regardless, since the SVG 1.2 DOM will require
DOM Level 3 XPath support.
To evaluate the constraint extension and compare to scripting we have taken the user interface example and implemented it in both CSVG with RCC/XSLT and standard SVG 1.2 with RCC/ECMAScript. There are 6 widgets used in the document: a window frame, a labelled grid layout, a text field, a checkbox miniform, a regular checkbox and a button bar. Each of these widgets is defined by a custom element. Figure 22 shows the instantiation of these custom elements to create the "Account details" window. This code is common to both the CSVG RCC/XSLT and the RCC/ECMAScript implementations.
1 <ex:frame id="f" x="100" y="24" width="352" height="350" title="Account details"> 2 <ex:labelledGrid> 3 <ex:row label="Username:"><ex:textField/></ex:row> 4 <ex:row label="Password:"><ex:textField/></ex:row> 5 </ex:labelledGrid> 6 <ex:checkBoxMiniForm label="Use HTTP proxy"> 7 <ex:labelledGrid> 8 <ex:row label="Hostname:"><ex:textField/></ex:row> 9 <ex:row label="Port:"><ex:textField/></ex:row> 10 </ex:labelledGrid> 11 </ex:checkBoxMiniForm> 12 <ex:checkBox label="Enable account"/> 13 <ex:buttonBar> 14 <ex:button label="Save changes"/> 15 <ex:button id="b" label="Discard changes"/> 16 </ex:buttonBar> 17 </ex:frame> |
Figure 22: User interface custom elements (code snippet)
The ex:frame
widget will lay out its child widgets vertically.
The ex:labelledGrid
widget will lay out widgets with a text
label to the left of each widget. Since the text labels and widgets are layed
out in two columns, the x coordinate of each widget will be equal, and positioned
such that none of the text labels are overlapped. ex:textField
is
implemented just as a rectangle that expands to fill all of the available width
of its viewport. The ex:checkBox
element is simple and just positions
a text label to the right of a box which can be toggled on and off.
ex:checkBoxMiniForm
defines a checkbox as the ex:checkBox
element does and also defines indented viewport to place child widgets. This viewport,
or miniform, is hidden if the checkbox is not currently toggled on. Finally,
the ex:buttonBar
widget will lay out buttons horizontally at the bottom
of its viewport and will place a line above these buttons. In all of the cases here,
widgets will take up all of the vertical space available to them in their viewports.
The height of the viewport for any widget will be the maximum space available but
typically the widget will not take up all of that space. The amount of vertical
space used up by a widget will determine the y coordinate of the following
widget's viewport, as the widgets are layed out down the screen.
In the interest of brevity, we will look at only the definition of one of the widgets
in the example — the c:checkBoxMiniPage
element.
Figure 23 and Figure 24
show the ECMAScript version and the CSVG version of the c:checkBoxMiniPage
widget, respectively.
1 <elementDef name="checkBoxMiniForm"> 2 <prototype> 3 <rect id="r" width="1em" height="1em" rx="3" ry="3" fill="#88c" stroke-width="1" stroke="#66a"/> 4 <text id="t" x="2em" y="1em"/> 5 <svg id="s" x="32" overflow="visible"/> 6 </prototype> 7 <script ev:event="SVGBindEnd"><![CDATA[ 8 var s = evt.target.shadowTree; 9 var t = s.getElementById("t"); 10 var r = s.getElementById("r"); 11 t.appendChild(document.createTextNode(evt.target.getAttributeNS(null, "label"))); 12 var b = getTextBBox(t); // t.getBBox(); 13 t.setAttributeNS(null, "y", r.height.baseVal.value / 2 + b.height / 2); 14 var child = evt.target.firstChild; 15 while (child.nodeType != 1) { 16 child = child.nextSibling; 17 } 18 var svg = s.getElementById("s"); 19 svg.setAttributeNS(null, "y", b.height + 16); 20 svg.setAttributeNS(null, "width", svg.width.baseVal.value - 32); 21 svg.setAttributeNS(null, "height", svg.height.baseVal.value - b.height - 16); 22 svg.appendChild(child.cloneNode(true)); 23 24 var domAttrModifiedListener = { 25 handleEvent: function(evt) { 26 if (evt.attrName == "label") { 27 var t = evt.target.shadowTree.getElementById("t"); 28 t.firstChild.nodeValue = evt.newValue; 29 } 30 } 31 }; 32 evt.target.addEventListener("DOMAttrModified", domAttrModifiedListener, false); 33 var clickListener = { 34 customElement: evt.target, 35 handleEvent: function(evt) { 36 var svg = evt.target; 37 while (svg.nodeType != 1 || svg.namespaceURI != svgns || svg.localName != "svg") { 38 svg = svg.nextSibling; 39 } 40 var checked = svg.getAttributeNS(null, "display") == "none"; 41 evt.target.setAttributeNS(null, "fill", checked ? "#88c" : "white"); 42 svg.setAttributeNS(null, "display", checked ? "inline" : "none"); 43 resize(document.getElementById("f")); // hack 44 } 45 }; 46 r.addEventListener("click", clickListener, false); 47 ]]></script> 48 </elementDef> |
Figure 23:
c:checkBoxMiniForm
widget implemented in RCC/ECMAScript (code snippet)
1 <script><![CDATA[ 2 // ... 3 function checkBoxClick(evt) { 4 var p = evt.target.parentNode; 5 while (p.getAttributeNS(null, "name") != "checked") { 6 p = p.previousSibling; 7 } 8 p.setAttributeNS(null, "value", p.getAttributeNS(null, "value") == "true()" ? "false()" : "true()"); 9 } 10 ]]></script> 11 12 <!-- ... --> 13 14 <xsl:template match="ex:checkBoxMiniForm"> 15 <c:variable name="checked" value="true()"/> 16 <svg y="8" overflow="visible"> 17 <rect width="1em" height="1em" rx="3" ry="3" stroke-width="1" stroke="#66a" onclick="checkBoxClick(evt)"> 18 <c:constraint attributeName="fill" value="c:if($checked, '#88c', 'white')"/> 19 <c:constraint attributeName="y" value="following-sibling::svg:text[1]/@y div 2 - c:LengthV('1em') div 2"/> 20 </rect> 21 <text x="0" y="0"> 22 <c:constraint attributeName="x" value="c:Length('1em') + 8"/> 23 <c:constraint attributeName="y" value="c:height(c:bbox(.))"/> 24 <xsl:value-of select="@label"/> 25 </text> 26 <svg x="32" y="0" overflow="visible"> 27 <c:constraint attributeName="y" value="preceding-sibling::svg:text[1]/@y + 16"/> 28 <c:constraint attributeName="width" value="c:LengthH('100%') - 32"/> 29 <c:constraint attributeName="height" value="c:LengthV('100%') - @y"/> 30 <c:constraint attributeName="display" value="c:if($checked, 'inline', 'none')"/> 31 <xsl:copy-of select="*"/> 32 </svg> 33 </svg> 34 </xsl:template> |
Figure 24:
c:checkBoxMiniForm
widget implemented in CSVG RCC/XSLT (code snippet)
We can see from these two examples that the RCC/ECMAScript version has a number
of shortcomings. Firstly, the script from line 8 to line 22 that is used to
set the computed attribute values of the shadow tree elements is much less
clear than the simple, one element per attribute syntax seen in the CSVG
version. Secondly we have the explicit DOM attribute modification event
listener that is set up to handle changes to the label
attribute
of the c:checkBoxMiniForm
element, from line 24 to 32. This
is simply not needed in the CSVG version, since the user agent will track
the dependencies and update the shadow tree automatically. We also find
in the ECMAScript version that the event handler used to capture mouse clicks on the checkbox
must explicitly relayout the widgets in the window due to its changing
size. Again, in CSVG this relayout is handled automatically due to the
constraints set up by the c:frame
element when it originally
layed out the widgets. The short checkBoxClick
script function
in the CSVG code only modifies the $checked
variable's value.
Since the display
attribute of the svg element containing the
miniform depends on this variable, it is automatically hidden or shown
whenever the variable is modified.
Taking the entire code for each of these two implementations it is obvious that there is much script used in the RCC/ECMAScript version to manually set up event handlers and effect updates to the shadow trees when attributes on custom elements are modified. The final file size of the CSVG implementation is approximately half the size of the RCC/ECMAScript version (12KB versus 24KB). [4]
At the time of writing, a pure ECMAScript version of the constraint extension was currently being written. This will allow authors using browsers other than Batik to utilise the extensions. Some aspects of CSVG cannot be exactly implemented in script, however, since access to the animation engine of the user agent is too limited. There is no way in SVG 1.1 to have an event fired when an animation tick occurs and so constraint propagation due to animations cannot be initiated immediately. Instead, some sort of polling process must be used to check if any animated values have changed. Also, there is no way to modify the animated values of XML attributes in the DOM without creating SMIL elements to effect the change. Thus, to cause updates to animated attributes the script will have to create and continually modify a SMIL set element. The mere existence of this element in the document could cause problems for XPath expressions written by the document author unless they are careful to know where they will be inserted and to avoid them.
Some extra features are being considered for inclusion in CSVG.
One is the use of SMIL timing
attributes on constraint elements. This would allow constraints to be
applied at certain times or in response to events fired. Also a new
element, tenantively named c:setExpression
, could be used similarly to the
standard SMIL set element to perform a one off property assignment. It
would use an expression to determine the value to assign to the property.
This element, in conjunction with the SMIL timing attributes, would help
avoid the small amount of script needed for the interactive examples
discussed earlier where CSVG variables are modified on mouse click.
These extra features could even be used to declaratively specify a complex interaction such as dragging and dropping a graphical element in the document. Figure 25 shows some hypothetical CSVG code to do this.
1 <svg> 2 <!-- Variables to store the offset of the mouse pointer when dragging begins --> 3 <c:variable name="dx" value="0"> 4 <c:constraint begin="r1.mousedown" attributeName="value" 5 value="c:clientX(c:event()) - id('r1')/@x"/> 6 </c:variable> 7 <c:variable name="dy" value="0"> 8 <c:constraint begin="r1.mousedown" attributeName="value" 9 value="c:clientY(c:event()) - id('r1')/@y"/> 10 </c:variable> 11 12 <!-- Background rectangle to capture mouse move events while dragging --> 13 <rect id="bg" x="0" y="0" width="100%" height="100%" fill="white" pointer-events="none"> 14 <set begin="r1.mousedown" end="mouseup" attributeName="pointer-events" to="all"/> 15 </rect> 16 17 <!-- Rectangle to be dragged --> 18 <rect id="r1" x="100" y="100" width="300" height="200"> 19 <set begin="mousedown" end="bg.mouseup" attributeName="pointer-events" to="none"/> 20 <c:constraint begin="bg.mousemove" attributeName="x" 21 value="c:clientX(c:event()) - $dx"/> 22 <c:constraint begin="bg.mousemove" attributeName="y" 23 value="c:clientY(c:event()) - $dy"/> 24 </rect> 25 </svg> |
Figure 25: Hypothetical drag and drop example using timing attributes on constraints (code)
We have described a simple extension to SVG 1.1 which allows attribute values to be specified with expressions whose value is evaluated at display time to determine the final layout and element appearance. The idea of using expression-based evaluation of attributes (under the name of one-way constraints) for specifying layout is not new. They have been used for widget layout, e.g. [Ell2002] , for page layout [WeW94] , [HMP03] , [JLSBS03] , layout of VRML [DK00] as well as for expression animation, e.g. [Dui87] .
The main contribution of the current paper is to demonstrate that adding expression-based evaluation of attributes to SVG adds little implementation effort but allows the document to adapt to different sized browsing windows, to adapt to changes in the size of text elements such as those arising from the use of a larger font or a different language, and to adapt to changes resulting from user interaction and animation. Remarkably all three kinds of adaptation are supported by the same simple mechanism. Such capabilities are increasingly important for user interaction, in particular for application user interfaces, and for universal access, where users may have unforseen viewing requirements.
Expression-based evaluation of attributes has two main advantages over script-based adaptation. The first is that it removes the need for the document author or authoring tool to worry about how to perform efficient incremental updates when document elements are modified. It is now the responsibility of the SVG agent to perform this automatically. Expression-based attributes would also allow style sheets to interact more tightly with the user agent. For example, a style could match a line thickness in a diagram to a font size without having to know the concrete font size in advance. The other main advantage of expression-based evaluation of attributes is that it removes most of the need for SVG authoring tools and document authors to understand script, thus facilitating editing and interchange of SVG documents.
The only reason that we can see for not providing expression-based evaluation of attributes in SVG is that future standards for generation of SVG elements from custom elements will provide the necessary adaptive layout. However as we have discussed in the Introduction our experience with CSVG suggests that even with custom elements it proves very useful to allow base SVG elements to have dynamically evaluated attribute values since this supports automatic, efficient update of the shadow tree when custom element attributes are modified as a result of user interaction or animation.
Batik project website: http://xml.apache.org/batik/
To ensure that the document remains consistent, any other c:variable
element in the scope of the expression between that variable and the
expression must be watched for modifications of its name
attribute. This is since the variable that the expression refers to
may change if a closer variable has its name changed.
Such a use of variables would be strange at best, and it is not clear
if it is worth the implementation effort required to track these sorts of changes.
Since the implementation was written, RCC has been dropped in favour of sXBL, a different language for specifying shadow tree generation. At the time of writing sXBL did not seem to afford shadow tree generation using XSLT.
The full code for the two implementations of the user interface widget layout example are located at http://www.csse.monash.edu.au/~clm/2004/07/widgets.html.
XHTML rendition created by gcapaper Web Publisher v2.1, © 2001-3 Schema Software Inc.