Using Java to Produce SVG Code in XHTML Data
Java Programming Notes # 2216
- Preface
- General background information
- Preview
- Discussion and sample code
- Run the programs
- Summary
- What's next?
- Complete program listings
- Copyright
- Resources
- About the author
Preface
General
This is the third lesson in a series designed to teach you how to write servlets to produce SVG code that will be rendered in graphic form by an SVG-compatible browser. In this lesson you will learn how to write Java code to produce XHTML files containing in-line SVG/XML code. You will also learn how to write servlets that produce XHTML output containing in-line SVG/XML code.
An SVG graphics library
In the previous two lessons titled "Java JAXP, Creating graphics using Java and SVG" and "An improved approach for creating SVG/XML code and SVG/XML DOM nodes using Java" (see Resources), I taught you how write your own SVG graphics library to eliminate, or at least alleviate the requirement to write raw XML code or raw JAXP DOM code. The use of the SVG graphics library makes it possible for you to produce SVG output simply by making typical Java method calls.
Viewing tip
I recommend that you open another copy of this document in a separate browser window and use the following links to easily find and view the figures and listings while you are reading about them.
Figures
- Figure 1. Graphic output from Svg03 and Svg04 programs.
Listings
- Listing 1. Beginning of Svg03 program class.
- Listing 2. Create the svg node.
- Listing 3. Show the outline of the canvas.
- Listing 4. Create the defs node.
- Listing 5. Create the first linearGradient node.
- Listing 6. The makeLinearGradient method.
- Listing 7. Create three stop nodes.
- Listing 8. Create the other linearGradient node.
- Listing 9. Create the radialGradient node.
- Listing 10. Create the node of type g.
- Listing 11. Create the basic ellipse.
- Listing 12. Set the appearance of the ellipse.
- Listing 13. Rotate the ellipse.
- Listing 14. Create the circle.
- Listing 15. Set the appearance and rotate the circle.
- Listing 16. Create and fill the rectangle.
- Listing 17. Round the corners of the rectangle.
- Listing 18. Create a polyline and rotate it.
- Listing 19. Create a polygon and dress it up.
- Listing 20. Create a wide, green, partially transparent line.
- Listing 21. Get an output stream to write the XHTML file.
- Listing 22. Process the DOM tree.
- Listing 23. The processDocumentNode method.
- Listing 24. Beginning of the recursive processNode method.
- Listing 25. Process nodes of type ELEMENT_NODE.
- Listing 26. The recursive processChildNodes method.
- Listing 27. The DOCUMENT_NODE case.
- Listing 28. First section of different code.
- Listing 29. Last section of different code.
- Listing 30. Program code for Svg03.
- Listing 31. Program code for Svg04.
Supplementary material
I recommend that you also study the other lessons in my extensive collection of online Java tutorials. You will find a consolidated index at www.DickBaldwin.com.
General background information
SVG is a language for describing two-dimensional graphics in XML. SVG allows for three types of graphic objects:
- vector graphic shapes
- images
- text
The Scalable Vector Graphics (SVG) 1.1 Specification is extensive, but it is not easy reading, particularly if your knowledge of XML is weak.
While I don't claim to be an expert in XML, I do know enough about the topic that I can usually make sense of the material provided in the SVG specification. One of my objectives for this series is to help Java programmers who lack a strong background in XML to take advantage of the capabilities of SVG.
Great graphics in the browser
|
Preview
|
In this lesson, I will present and explain two different programs. The first program named Svg03 produces an output file containing SVG data embedded in XHTML data (see the sidebar). The file can be rendered by loading it directly into a Firefox 1.5 browser.
The second program named Svg04 is a servlet program. When the servlet is deployed and then accessed by a Firefox 1.5 browser, the servlet returns SVG data embedded in XHTML data (see the sidebar), which is rendered in graphic form by a Firefox 1.5 browser.
The graphic output
Both programs produce the same graphic output, which is shown in Figure 1.
Figure 1. Graphic output from Svg03 and Svg04 programs.
|
|
The programs illustrate a large number of important SVG capabilities
The graphic output shown in Figure 1 illustrates a large number of important capabilities of SVG including the following:
- An svg element that contains other SVG elements and serves as a canvas upon which SVG graphics elements are drawn. The available graphics elements are listed below. The subset of graphics elements shown in boldface in the following list are known as the Basic Shapes. Each of the basic shapes is illustrated in Figure 1. The other graphics elements will be illustrated in future lessons.
- path
- text
- rect
- circle
- ellipse
- line
- polyline
- polyline
- image
- use
- A rect element that produces a black border (stroke) on the canvas with a thickness of one pixel.
- A defs element, which is a container for elements that are referenced by other elements in the SVG code. In this case, the defs element contains two linearGradient elements, one radialGradient element, and several stop elements associated with the gradient elements.
- A linearGradient element that produces a left-to-right color gradient that begins with yellow, changes to red, and finishes with blue. This gradient is referenced to fill the ellipse.
- A second linearGradient element that produces a color gradient that begins with green and finishes with blue. This gradient is referenced to fill the circle.
- A radialGradient element, which produces a color gradient where the color changes going outward from a center point. The rounded rectangle in Figure 1 is filled with this radial gradient where the color starts as yellow at the center, changes to red, and finishes with blue at the outer extremity of the gradient.
- Several stop elements, which determine where and how the changes in color gradients occur.
- A g element, which is a container element for grouping together related graphics elements. This g element is the container for all the graphics elements in Figure 1 other than the rectangle that forms the black border on the canvas.
- An ellipse, with a blue border (stroke) two pixels thick rotated by fifteen degrees clockwise around its center and filled with a three-color linear gradient.
- A circle rotated by fifteen degrees clockwise around its center and filled with a two-color linear gradient.
- A rectangle with rounded corners filled with a three-color radial gradient.
- A polyline described by four points and rotated by ten degrees clockwise around the first point on the left.
- A polygon described by four points with a red border (stroke) three pixels thick and filled with green.
- A green line with a width of twelve pixels and an opacity of 0.6. This line is drawn on top of the other graphics elements from the upper left to the bottom right of the canvas. Note that the other graphics elements can be seen showing through the green line because its opacity is less than 1.0.
As I discuss the code for the two programs in the next section, remember that both of the programs produce the graphic output shown in Figure 1.
Discussion and sample code
The program named Svg03
Description of the program
The main purpose of this program is to illustrate the inclusion of SVG graphics elements in an XHTML file that can be rendered in Firefox 1.5.
This program uses an SVG graphic library of my own design that has been significantly upgraded relative to the version used in the earlier lessons of this series (see Resources). The upgraded version of the library supports the following basic shapes plus linear and radial gradients and dozens of attributes and transforms on each shape.
- rect
- circle
- ellipse
- line
- polyline
- polygon
The shapes in the above list are all of the basic shapes provided by SVG.
Convenience methods
The library also contains some convenience methods for creating elements, nodes, and blocks of code. These convenience methods are designed to reduce the labor required to write programs such as this one.
Create and transform a DOM tree
This program creates a DOM tree describing the SVG code for the image shown in Figure 1. Then it transforms the DOM tree into SVG code that is embedded in a file that would otherwise be a valid XHTML file. As mentioned earlier, inclusion of the SVG code prevents the code from being valid XHTML code because the SVG element names are not recognized by XHTML validator programs
|
The capability demonstrated by this program is a precursor to being able to create inline SVG code in a servlet and to cause that SVG code to be rendered in a Firefox browser that accesses the servlet.
The output file produced by this program can be rendered by loading it directly into Firefox 1.5.
The program was tested using J2SE 5.0, Firefox v1.5.0.9, and WinXP.
Will discuss in fragments
I will discuss and explain this program in fragments. You can view the entire program in Listing 30 near the end of the lesson. Listing 1 shows the beginning of the program class for the program named Svg03.
Listing 1. Beginning of Svg03 program class.
public class Svg03{
public static void main(String argv[]){
//The following data values will be used to create
// SVG graphics.
int ellipseCenterX = 110;
int ellipseCenterY = 100;
int ellipseWidth = 100;
int ellipseHeight = 40;
int ellipseRotate = 15;//degrees
int circleCenterX = 110;
int circleCenterY = 100;
int circleRadius = 30;
int circleRotate = 15;
int rectCenterX = 110;
int rectCenterY = 200;
int rectWidth = 110;
int rectHeight = 80;
int rectRoundX = 26;
int rectRoundY = 25;
int rotatePolyline = 10;
try{
//Create a DOM tree that describes a particular
// graphic image.
//Begin by creating a Document object and a root
// node named svg. All of the graphic content will
// be contained in the svg element. This code was
// explained in the earlier program named
// Xslt01.java that was explained in lesson 2202
// named "Getting Started with Java JAXP and XSL
// Transformations (XSLT)".
//At this point, the program starts using the
// SVG graphics library encapsulated in the class
// named SvgGraphics.
Document document = SvgGraphics.getDocument();
|
Listing 1 begins by declaring and initializing some variables that will be used later by the program
Then the program calls the getDocument method of the SvgGraphics class to get a reference to the Document object that represents the entire XML document. I explained this method in an earlier lesson and won't repeat that explanation here.
Create the svg node
Listing 2 calls the makeNode method to create the root node named svg and append it to the document node. Listing 2 also sets some attributes on the svg node that are required for proper rendering.
Listing 2. Create the svg node.
Element svg = SvgGraphics.makeNode(
document,
null,//parent
"svg",//node type
new String[]{"xmlns","http://www.w3.org/2000/svg",
"version","1.1",
"width","220px",
"height","440px",
"position","static",
"top","0",
"left","0"
});//end call to makeNode
|
As I mentioned earlier, the svg node is used to create the canvas upon which the Basic Shape graphics elements shown in Figure 1 are drawn.
The makeNode method
I explained the usage of the method named makeNode in the earlier lesson titled "An improved approach for creating SVG/XML code and SVG/XML DOM nodes using Java" (see Resources) and won't repeat that explanation here.
|
With the possible exception of the attributes named xmlns and position, the purpose of the attributes that are set on the svg node in Listing 2 should be fairly obvious on the basis of their names.
Show the outline of the canvas
Listing 3 calls the makeNode method to create a node of type rect to draw the black rectangular outline on the canvas in Figure 1.
Listing 3. Show the outline of the canvas.
Element outline = SvgGraphics.makeNode(
document,
svg,//parent could be null
"rect",//node type
new String[]{"x","0",
"y","0",
"width","220",
"height","440",
"fill","none",
"stroke","black",
"stroke-width","1"
});//end call to makeNode
|
The stroke and stroke-width attributes
The stroke attribute value in Listing 3 specifies that the rectangle should be drawn (or stroked) with a black line. The stroke-width attribute in Listing 3 specifies that the black line should be one pixel in thickness.
The x, y, width, and height attributes
If you compare the x, y, width, and height attribute values in Listing 3 with the left, top, width, and height attribute values in Listing 2, you will see that the outline of the black rectangle matches the outline of the canvas that was created by Listing 2. You can't see the canvas in Figure 1. However, you can see its outline that is highlighted by the black rectangle.
Create the defs node
Listing 4 calls the makeNode method to create a node of type defs.
Listing 4. Create the defs node.
Element defs = SvgGraphics.makeNode(document,
svg,//parent
"defs",
null);
|
The defs node will be the parent (container) for three gradient nodes. As explained earlier, the defs element will contain two linear gradient elements and one radial gradient element.
In this case, a value of null is passed as the fourth parameter (which is normally a reference to an array object containing attribute name/value pairs) to indicate that the defs node has no attributes.
Create the first linearGradient node
Listing 5 is the beginning of a section of code that creates nodes that define three different gradient coloring schemes. These nodes will be child nodes of the defs node.
Listing 5. Create the first linearGradient node.
Element gradientA = SvgGraphics.makeLinearGradient(
document, //this document
defs, //parent
"gradientA");//id
|
A container for referenced elements
|
The gradient elements will be referenced later to specify the fill colors for an ellipse, a circle, and a rounded rectangle.
Call the makeLinearGradient method
The code in Listing 5 calls the makeLinearGradient method to create the node of type linearGradient for gradientA. Although it isn't obvious yet, the gradient element that results from transforming this node into raw XML code will provide a linear gradient that begins with yellow, changes to red, and ends with blue going from left to right.
Did not call the makeNode method
|
It is important to note that Listing 5 did not call the general makeNode method to create this node. Rather, it called the more specialized method named makeLinearGradient.
Specialized methods in my SVG graphics library
My SvgGraphics library contains a number of specialized methods that are used to create nodes with most of the attributes having default values. Only the attribute values that are most likely to change from one call to the next are passed as parameters to these methods. They are provided as a convenience because they can be called with a little less effort and a little less thought than the makeNode method for those cases where they are applicable.
The makeLinearGradient methodIt will probably be useful to take a look at the code in the makeLinearGradient method as an example of the more specialized methods in the library. The method is shown in its entirety in Listing 6.
Listing 6. The makeLinearGradient method.
static Element makeLinearGradient(Document document,
Element parent,
String id){
Element gradient =
(Element)document.createElement("linearGradient");
parent.appendChild(gradient);
gradient.setAttribute("id",id);
return gradient;
}//End makeLinearGradient
|
This method creates and returns a reference to a node of type linearGradient with an id attribute whose value is set to the value of the third parameter. Before returning, the new node is appended as a child to the node whose reference is received as the second parameter.
This method is straightforward and shouldn't require any further explanation.
|
Now, returning to the main thread of the discussion, a linearGradient element alone isn't very useful. To be useful, the linearGradient element must be supplemented by stop elements that control how and where the colors change. One stop element is required for each color change. The stop elements must be children of the linearGradient element.
According to the W3C, "The ramp of colors to use on a gradient is defined by the stop elements that are child elements to either the linearGradient element or the radialGradient element."
Call the makeGradientStop method
Listing 7 calls the makeGradientStop method three times in succession to establish the "ramp of colors" to be used on the linearGradient node that was created in Listing 6.
Listing 7. Create three stop nodes.
SvgGraphics.makeGradientStop(document,
gradientA,//parent
"2%", //start here
"yellow");//color
SvgGraphics.makeGradientStop(document,
gradientA,
"50%",
"red");
SvgGraphics.makeGradientStop(document,
gradientA,
"98%",
"blue");
|
You can view the makeLinearGradient method in its entirety in Listing 30, but you don't need to know much about the code in the method to be able to use it. The important thing is to understand the meaning of the four parameters to the method.
The four parameters to the makeGradientStop method
The first parameter is a reference to the document to which the element belongs, and the second parameter is a reference to the node that will be the parent of the stop node.
The gradient vector
The third parameter is perhaps the most complicated of the four. This parameter specifies the location, (proceeding from the beginning to the end along a linearGradient vector or from the center point outward for a radialGradient vector) at which the color starts changing.
|
For this example, the color starts as pure yellow at the left end of the ellipse and starts changing to red two-percent of the way to the right end of the ellipse.
The specified colors
The fourth parameter simply specifies the color at the point specified by the third parameter.
Thus the combination of the first two stop nodes in Listing 7 specify that the color will be yellow at and to the left of the two-percent point and will be red at the fifty-percent point. The color will change in a linear gradient fashion from yellow to red along the portion of the gradient vector that connects those two points.
Similarly, the combination of the last two stop nodes in Listing 7 specify that the color will be red at the fifty-percent point and will be blue at the ninety-eight-percent point and beyond. The color will change in a linear gradient fashion from red to blue along the portion of the gradient vector that connects those two points.
Create the other linearGradient node
Listing 8 creates the linearGradient node and two child nodes of type stop that result in the green-to-blue gradient in the circle in Figure 1.
Listing 8. Create the other linearGradient node.
Element gradientB =SvgGraphics.makeLinearGradient(
document, //this document
defs, //parent
"gradientB");//id
SvgGraphics.makeGradientStop(document,
gradientB,//parent
"0%", //start here
"green"); //color
SvgGraphics.makeGradientStop(document,
gradientB,
"100%",
"blue");
|
Create the radialGradient node
Listing 9 calls the makeRadialGradient method once and then calls the makeGradientStop method three times in succession to create the radial gradient node that results in the color gradient shown in the rounded rectangle in Figure 1. This gradient starts with yellow at the center, changes to red at a distance of fifty-percent of the distance to the outer edge and ends up with blue at the outer edge of the rounded rectangle.
Listing 9. Create the radialGradient node.
Element gradientC =
SvgGraphics.makeRadialGradient(
document, //this document
defs, //parent
"gradientC", //ID
"userSpaceOnUse",
rectCenterX, //cx
rectCenterY, //cy
rectWidth/2); //r
SvgGraphics.makeGradientStop(document,
gradientC,
"0%",
"yellow");
SvgGraphics.makeGradientStop(document,
gradientC,
"50%",
"red");
SvgGraphics.makeGradientStop(document,
gradientC,
"100%",
"blue");
|
The parameters to the makeRadialGradient method are similar to, but not identical to the parameters to the makeLinearGradient method in Listing 5 and Listing 8. For the radial gradient, the first three parameters have the same meaning as in Listing 5.
The attribute value of "userSpaceOnUse"
I'm not even going to try to explain the meaning of the fourth parameter with the value "userSpaceOnUse." For this parameter, you can either continue to use the same value, or you can go to the SVG specifications (see Resources) to learn more about this parameter.
The center point and radius of the radialGradient
The fifth and sixth parameters, cx and cy, specify the point from which the gradient will radiate.
The seventh parameter, r specifies the distance from that center point that represents 100% insofar as the stop element is concerned. Stated differently, this distance represents the length of the gradient vector discussed earlier.
The fill color results
Thus, when this radial gradient is used to fill the rounded rectangle shown in Figure 1, the color at the center of the rectangle will be yellow. The color half way between the center and the outer edge on the horizontal axis will be red. The color at the outer edge on the horizontal axis will be blue. The colors will change in a linear gradient fashion along any vector that extends from the center to a point that is at a distance from the center equal to one-half the width of the rectangle. (As you can see in Figure 1, because the height of the rectangle is less than the width, the color never quite reaches the blue state at the top and the bottom of the rectangle.)
The center point can be anywhere
By the way, there is no requirement for the center point of the radial gradient to be at the center of the rectangle. In fact, there is no requirement for the center point of the radial gradient to even be inside the rectangle. When the radial gradient is used to fill the rectangle, the effect is as if an invisible version of the radial gradient is created at a specified location with a specified radius on the canvas but only that portion that overlaps the rectangle actually becomes visible.
Create the node of type g
Listing 10 calls the makeNode method to create a group node named g. As described earlier, a g element is a container element for grouping together related graphics elements. This g node will result in an element that will be the container for an ellipse, a circle, a rectangle, a polyline, a polygon, and a line.
Listing 10. Create the node of type g.
Element g = SvgGraphics.makeNode(document,
svg,//parent
"g",
null);
|
Because this node doesn't have any attributes, a value of null is passed as the fourth parameter to the method.
Create the basic ellipse
Listing 11 shows the beginning of a section of code that creates the ellipse shown at the top of Figure 1. Eventually, this ellipse will be:
- Given a blue border that is two pixels thick.
- Filled with gradientA, which is a linear gradient that begins with yellow, changes to red, and finishes with blue.
- Rotated by fifteen degrees clockwise around its center.
|
Listing 11. Create the basic ellipse.
Element theEllipse = SvgGraphics.makeEllipse(
document,
g,//Owner
ellipseCenterX,
ellipseCenterY,
ellipseWidth,
ellipseHeight);
|
Note that the ellipse node that is created in Listing 11 is appended as a child of the g node.
Set or update the appearance of the ellipse
After the basic ellipse node has been created by the code in Listing 11, Listing 12 calls the setAttribute method on the reference to the ellipse node three times in succession to:
- Fill the ellipse with the linear gradient element that was created and identified as gradientA earlier. (This is what I meant when I discussed the possibility of SVG elements referencing elements that are created within the defs element.)
- Set the border color to blue.
- Set the border thickness to two pixels.
Listing 12. Set the appearance of the ellipse.
theEllipse.setAttribute("fill","url(#gradientA)");
theEllipse.setAttribute("stroke","blue");
theEllipse.setAttribute("stroke-width","2");
|
|
The transform attribute can be set repeatedly on a graphics element to perform a series of sequential transforms on the element. Available transforms include:
- translate
- scale
- rotate
- skewX
- skewY
Rotate the ellipse (the hard way)
In this case, I was interested only in rotating the ellipse about its center. It should be possible to rotate a shape around any location specified by x and y coordinate values. However, I was unable to identify the proper syntax for doing that, so I used a work around that consisted of three sequential steps to accomplish the desired rotation.
The three steps
The three steps were:
- Translate the origin so that the origin was at the center of the ellipse.
- Rotate the ellipse by fifteen degrees clockwise around the origin.
- Translate the origin so that the center of the now-rotated ellipse was back in its original location relative to the origin.
The code to accomplish the three steps is shown in Listing 13.
Listing 13. Rotate the ellipse.
theEllipse.setAttribute("transform",
"translate(" + ellipseCenterX + ","
+ ellipseCenterY + ") "
+ "rotate(" + ellipseRotate + ") "
+ "translate(" + (-ellipseCenterX) + ","
+ (-ellipseCenterY) + ") ");
|
If Listing 13 seems confusing, rewrite the statement in Listing 13, substituting the numeric values for the variables in place of the variable names. It may make more sense if you look at it that way.
Create the circle
Listing 14 calls the makeCircle method to position a basic circle so that it appears to be inside the ellipse in Figure 1. However, it isn't really inside the ellipse in a containment sense. Rather, the circle is simply positioned on top of the ellipse. The default opacity value for the circle is 1.0. Therefore, the ellipse doesn't show through the circle.
As you can see, the parameter list for the makeCircle method in Listing 14 is a little simpler than the parameter list for the makeEllipse method in Listing 11. This is because it is necessary to specify both a width and a height for an ellipse, but it is only necessary to specify a radius for a circle.
Listing 14. Create the circle.
Element theCircle = SvgGraphics.makeCircle(
document,
g,//Owner
circleCenterX,
circleCenterY,
circleRadius);
|
Once the code in Listing 14 has finished executing, the basic circle has been created and positioned, but it hasn't been "dressed up" in the manner shown in Figure 1.
Set the appearance and rotate the circle
Listing 15 calls the setAttribute method twice in succession to:
- Fill the circle with the green-to-blue linear gradient, created and identified earlier as gradientB, as shown in the circle in Figure 1.
- Rotate the circle by fifteen degrees clockwise around its center.
Listing 15. Set the appearance and rotate the circle.
theCircle.setAttribute("fill","url(#gradientB)");
theCircle.setAttribute("transform",
"translate(" + circleCenterX + ","
+ circleCenterY + ") "
+ "rotate(" + circleRotate + ") "
+ "translate(" + (-circleCenterX) + ","
+ (-circleCenterY) + ") ");
|
Why rotate the circle?
You might be wondering what is to be gained by rotating a circle by fifteen degrees around its center. If the circle had been filled with a solid color, rotation would not change the appearance of the circle. However, because the circle was filled with a linear gradient such that it is green on the left and blue on the right, rotating the circle about its center does change its appearance.
Create and fill the rectangle
Listing 16 calls the makeRect method to create a basic rectangle. The coordinates of the upper-left corner of the rectangle are specified by the values of the third and fourth parameters. The width and height of the rectangle are specified by the values of the fifth and sixth parameters.
Listing 16. Create and fill the rectangle.
Element theRect = SvgGraphics.makeRect(
document,
g,//Owner
rectCenterX - rectWidth/2,//x
rectCenterY - rectHeight/2,//y
rectWidth,
rectHeight);
theRect.setAttribute("fill","url(#gradientC)");
|
Then Listing 16 calls the setAttribute method to fill the rectangle with the radial gradient created and identified earlier as gradientC.
Note that at this point, the rectangle still has square corners. It is not yet a "rounded rectangle."
Round the corners of the rectangle
Listing 17 calls the setAttribute method twice in succession to set the values for the attributes named rx and ry. The purpose of these two attributes is to control the amount of rounding that is applied to the corners.
Listing 17. Round the corners of the rectangle.
//Round the corners.
theRect.setAttribute("rx",""+ rectRoundX);
theRect.setAttribute("ry",""+ rectRoundY);
|
Although I can't explain exactly how these attribute values are used to round the corners from a geometry viewpoint (I would like to see some explanatory pictures that describe this process), I can tell you generally how they behave. If you set either attribute value to zero, all four corners will be square.
If you set rx to half the width of the rectangle and set ry to half the height of the rectangle, the corners will be rounded so much that the rectangle will take on the appearance of an ellipse.
Values in between those two extremes result in rounded corners as shown in Figure 1.
What is a polyline?
A polyline is a shape that results from "connecting the dots" between each consecutive pair of points in a set of points. Each point is connected to only the point that follows it in the specification of the locations of the points.
There is no requirement for the shape to be closed, although it can be closed if the coordinates for the last point are the same as the coordinates for the first point. (The polyline in Figure 1 is not closed.)
There is also nothing to prohibit a line drawn between two points from crossing a previously drawn line between two other points as shown in Figure 1.
There are no gaps or open spaces along the line. You can always start at the beginning specified by the first point and follow the line to the end that is specified by the last point.
Create a polyline and rotate it
Figure 1 shows a black polyline that was drawn using coordinate values for the points as shown in Listing 18.
Listing 18 begins by creating and populating an array object containing eight values of type int. Each consecutive pair of int values placed in the array object represents the coordinates of a single point. Thus, the array object contains the coordinate values for four points.
Call the makePolyline method
Then Listing 18 calls the makePolyline method to construct and return a reference to a node that describes a polyline element representing the four points whose coordinate values were used to populate the array object.
Listing 18. Create a polyline and rotate it.
//Draw a polyline with four points.
int[] polylinePoints =
{10,235,210,235,110,275,110,225};
Element polyline = SvgGraphics.makePolyline(
document,
g,//owner
polylinePoints);
//Rotate the polyline by 10 degrees around the first
// point.
polyline.setAttribute("transform",
"translate(" + polylinePoints[0] + ","
+ polylinePoints[1] + ")"
+ "rotate(" + rotatePolyline + ")"
+ "translate(" + (-polylinePoints[0])
+ "," + (-polylinePoints[1]) + ")");
|
Rotate the polyline around the first point
When the makePolyline method returns the reference to the polyline node, the code in Listing 18 calls the setAttribute method to rotate the entire polyline shape by ten degrees clockwise around the first point. Thus, the first line drawn for the polyline in Figure 1 has a general downward slope going from left to right.
What is a polygon?
The big difference between a polyline and a polygon is that a polygon must always be a closed shape. As a result, once the polygon points have been connected with straight lines in the same fashion as a polyline, the SVG rendering engine draws one additional line for a polygon that connects the first and last points. This additional line always closes the shape.
Create a polygon and dress it up
Listing 19 calls the makePolygon method to create a polygon node using a set of points similar to those used earlier to create the polyline node. However, the polygon is not subsequently rotated as was the case with the polyline. Rather, the setAttribute method is called on the reference to the polygon node three times in succession to give the polygon a red border that is three pixels thick, and to fill the polygon with the solid color green.
Listing 19. Create a polygon and dress it up.
//Draw a polygon with four points. Give it a red
// border and fill it with green.
int[] polygonPoints =
{10,335,210,335,110,375,110,325};
Element polygon = SvgGraphics.makePolygon(
document,
g,//parent
polygonPoints);
polygon.setAttribute("fill","green");
polygon.setAttribute("stroke","red");
polygon.setAttribute("stroke-width","3");
|
The green and red polygon is shown at the bottom of Figure 1.
Create a wide, green, partially transparent line
Listing 20 calls the makeLine method to create a line node that extends from the upper-left corner of the canvas to the lower-right corner of the canvas. By default, this would be a black line one pixel wide.
Listing 20. Create a wide, green, partially transparent line.
Element line = SvgGraphics.makeLine(document,
g, //owner
0, //x1
0, //y1
220, //x2
440);//y2
line.setAttribute("stroke","green");
line.setAttribute("stroke-width","12");
line.setAttribute("stroke-opacity","0.6");
|
Then Listing 20 calls the setAttribute method three times in succession to:
- Set the color of the line to green.
- Set the width of the line to twelve pixels.
- Set the opacity of the line to 0.6 or 60-percent. (You could also say that the line is 40-percent transparent.)
Because the line is the last shape to be drawn on the canvas in Figure 1, it is drawn on top of the other shapes. However, because the opacity of the line is 0.6 (as opposed to 1.0), it is partially transparent and the other shapes that are behind the line show through the line.
Wrap the SVG code in XHTML data
At this point, a DOM tree that represents the SVG code for the graphic image shown in Figure 1 has been constructed. The remaining code in the class is used to transform the DOM tree into SVG code and to wrap the SVG code in a document that would otherwise be a valid XHTML document. This process begins in Listing 21 and becomes recursive later on.
Listing 21 begins by instantiating an object of the class. Then it gets an output stream that will be used to write the XHTML data and the SVG data into an output file named junk.xhtml.
Listing 21. Get an output stream to write the XHTML file.
//Instantiate an object of this class
Svg03 thisObj = new Svg03();
//Get an output stream for the output produced by
// the program code.
PrintWriter out = new PrintWriter(
new FileOutputStream("junk.xhtml"));
|
Process the DOM tree
The last executable statement in the main method is shown in Listing 22. This statement calls the method named processDocumentNode passing a reference to the output stream and a reference to the Document node of the DOM tree as parameters.
Listing 22. Process the DOM tree.
thisObj.processDocumentNode(out,document);
}catch(Exception e){
//Note that no effort was made to provide meaningful
// information in the event of an exception or
// error.
e.printStackTrace(System.err);
}//end catch
}// end main()
|
An XHTML output file is required
If you have studied the two previous lessons in this series (see Resources), you will recall that the sample programs in those lessons produced SVG files as the main output from the program. Those programs didn't attempt to create XHTML files. You will also recall that the programming task was relatively easy at this point in the program because JAXP methods already exist to do transform the DOM tree into raw XML code. Unfortunately, things aren't quite so easy when the program is required to produce an XHTML file as its output. There is a lot of programming that you are responsible for providing in this case.
XHTML text is required
There is quite a lot of XML text that is required to cause a file to qualify as a valid XHTML file. I won't go into the reasons for that text because that is beyond the scope of this tutorial series. However, I will show you how to write the code to create the text.
The processDocumentNode method
The processDocumentNode method is used to produce the XML text that is required in the output at the document level, (such as the XML declaration). The method also produces the top level element tags.
The processDocumentNode method is shown in its entirety in Listing 23.
Listing 23. The processDocumentNode method.
void processDocumentNode(PrintWriter out,Node node){
//Create the beginning of the XHTML document.
out.println("<?xml version=\"1.0\" "
+ "encoding=\"UTF-8\"?>");
out.println(
"<!DOCTYPE html PUBLIC \"-//W3C//DTD "
+ "XHTML 1.0 Transitional//EN\" "
+ "\"http://www.w3.org/TR/xhtml1/"
+ "DTD/xhtml1-transitional.dtd\">");
out.println(SvgGraphics.makeElement(false,"html",
new String[]{"xmlns","http://www.w3.org/1999/xhtml",
"xml:lang","en",
"name","lang"
})//end call to makeElement
);//end println
out.println("<head>");
out.println(SvgGraphics.makeElement(
true,
"meta",
new String[]{"http-equiv","content-type",
"content",
"image/svg+xml; charset=UTF-8"
})//end call to makeElement
);//end println
out.println("<title>Generated XHTML file</title>");
out.println("</head>");
out.println(SvgGraphics.makeElement(
false,
"body",
new String[]{"id","body",
"style","position:absolute;"
+ "z-index:0;"
+ "border:1px solid black;"
+ "left:0;"
+ "top:0;"
+ "width:220px;"
+ "height:440px;"
})//end call to makeElement
);//end println
//Go process the root (document) node. This method
// call triggers a recursive process that will
//process the entire DOM tree.
processNode(out,node);
//The entire DOM tree has been processed when control
// returns to this point.
//Now finish the output document and flush the output
// buffer.
out.println("</body></html>");
out.flush();
}//end processDocumentNode
|
Behavior of the processDocumentNode method
|
The behavior of the processDocumentNode method is pretty straightforward and shouldn't require a lot of explanation beyond the embedded comments.
Down to the point where the method makes a recursive call to the method named processNode, the code in the method consists simply of print statements and calls to the makeElement method.
The call to the processNode method
The call to the processNode method near the end of the processDocumentNode method is where the recursive processing of the DOM tree begins in earnest. (The processNode method is passed a reference to the output stream along with a reference to the root node of the DOM tree at this point.)
When control returns to the processDocumentNode method from the call to the processNode method in Listing 23, the entire DOM tree will have been processed recursively. At that point, one more print statement is executed to put the final string of XML text into the output file. Then the output buffer is flushed, the processDocumentNode method terminates, and the program terminates leaving an XHTML file named junk.xhtml in the current directory.
The processNode method
A general DOM tree could contain any of the following seven types of nodes:
- root or document
- element
- attribute
- text
- comment
- processing instruction
- namespace
A general purpose DOM tree processor would have to be capable of handling any combination of the seven types of nodes in the above list. However, because the nodes in this DOM tree are produced by the program, it is known in advance which types of nodes will be encountered. Therefore, the processNode method only handles element nodes, attribute nodes, and document nodes.
Beginning of the recursive processNode method
The recursive processNode method begins in Listing 24. The code in Listing 24 is straightforward.
Listing 24. Beginning of the recursive processNode method.
void processNode(PrintWriter out,Node node){
try{
if (node == null){
System.out.println("Nothing to do, node is null");
return;
}//end if
|
Process nodes according to their type
Each time the processNode method is called, it receives an incoming parameter that specifies the node from the DOM tree that is to be processed recursively. Each node is processed according to its type. A switch statement is used to select among the different types.
Process nodes of type ELEMENT_NODE
Listing 25 shows the code that is executed to process nodes of type ELEMENT_NODE.
Listing 25. Process nodes of type ELEMENT_NODE.
int type = node.getNodeType();
switch(type){
case Node.ELEMENT_NODE:{
//Start creating the start tag.
String nodeName = node.getNodeName();
out.print("<" + nodeName + " ");
//Now add each attribute to the start tag.
NamedNodeMap theList = node.getAttributes();
int length = theList.getLength();
for(int cnt = 0;cnt < length;cnt++){
Node item = theList.item(cnt);
out.print(item.getNodeName() + "=\""
+ item.getNodeValue() + "\" ");
}//end for loop
out.println(">");//close the start tag.
//Process all XML child nodes recursively.
processChildNodes(out,node);
//The element and all of its children have been
// processed when control returns to this point.
//Create the end tag for the element.
out.println("</" + nodeName + ">");
break;
}//end case ELEMENT_NODE
|
Create the beginning of the element start tag
The code in Listing 25 begins by creating the XML text that constitutes the beginning of the start tag for an XML element consisting of "<nodeName ".
Process each attribute
Then Listing 25 uses a for loop to:
- Get each attribute belonging to the node
- Construct an XML text string in the proper syntax for the name and value of the attribute
- Concatenate the newly-constructed text string onto the start tag
Finish the start tag
When all attributes belonging to the node have been processed, the for loop terminates and an angle bracket is appended to the element start tag. At this point, a start tag looking something like the following has been sent to the output stream:
<elementName attrName="attrValue" attrName="attrValue">
Process the child nodes
Before creating the element's end tag, it is necessary to process any child nodes belonging to the node that is being processed. This is accomplished by making a call to the recursive method named processChildNodes.
Construct the end tag
When control returns from the recursive processChildNodes method, the entire DOM sub-tree below the current node will have been processed. The code in Listing 25:
- Constructs the end tag for the element
- Sends it to the output stream
- Breaks out of the switch statement to return control to the method from which the processNode method was called
Put switch statement on the back burner
At this point, I am going to put the switch statement in Listing 25 on the back burner, and continue explaining the processing of nodes of type ELEMENT_NODE.
As mentioned above, before creating the text for the element's end tag and sending it to the output, the code in Listing 25 calls the recursive method named processChildNodes, passing a reference to the node currently being processed, along with a reference to the output stream as parameters. The purpose is to recursively process all of the child nodes belonging to the node that is currently being processed.
The recursive processChildNodes method
The processChildNodes method is shown in its entirety in Listing 26.
Listing 26. The recursive processChildNodes method.
void processChildNodes(PrintWriter out,Node node){
NodeList children = node.getChildNodes();
if (children != null){
int len = children.getLength();
//Iterate on NodeList of child nodes.
for(int i = 0; i < len; i++){
//This is a recursive call.
processNode(out,children.item(i));
}//end for loop
}//end if children != null
}//end processChildNodes
|
|
The code in Listing 26 begins by getting a list of all the child nodes belonging to the node specified by the second incoming parameter.
Process each child node
Then it executes a for loop, calling the processNode method during each iteration of the loop and passing a child node's reference to the processNode method. (This causes the specified child node and all of its children to be processed recursively.)
When the for loop in Listing 26 terminates, all of the child nodes belonging to the node that was specified by the second incoming parameter will have been recursively processed, and the processChildNodes method returns control to the method from which it was called.
The DOCUMENT_NODE case
That completes the explanation of the processing of nodes of type ELEMENT_NODE that began with the switch statement in Listing 25. There is another case in the switch statement, however, that hasn't been explained yet. That case is shown in Listing 27.
Listing 27. The DOCUMENT_NODE case.
case Node.DOCUMENT_NODE:{
//No action is required other than to process
// the child nodes recursively.
processChildNodes(out,node);
break;
}//end case DOCUMENT_NODE
}//end switch
}catch(Exception e){
e.printStackTrace(System.err);
}//end catch
}//end processNode(Node)
|
Listing 27 shows the code that processes the incoming node to the processNode method for the case where the type of the node is DOCMENT_NODE.
Will occur only once
This case will occur only once at the beginning of the processing of the DOM tree because the DOM tree has only one document node. As indicated in the comments in Listing 27, no action is required for this case other than to assure that all of the child nodes of the document node are processed recursively. Therefore, the code in Listing 27 simply makes a call to the processChildNodes method passing a reference to the document node as a parameter.
The entire DOM tree will have been processed
When control returns from the call to processChildNodes in the DOCUMENT_NODE case in Listing 27, the entire DOM tree will have been processed. At that point:
- Control breaks out of the switch statement
- The processNode method returns to the method from which it was called
- Control progresses up the call stack to the bottom of Listing 23 causing the final XML text to be written into the output stream and causing the output buffer to be flushed
The program terminates
Control then returns to the main method in Listing 22. Since there is nothing more for the program to do in the main method, the program terminates at that point, leaving a new file named junk.xhtml in the current directory.
End of explanation for Svg03
And that ends the explanation of the program named Svg03. As indicated earlier, you will find a complete listing of all of the code discussed above, plus the code for my SVG graphics library class named SvgGraphics in Listing 30 near the end of the lesson.
The program named Svg04
Description of the program
This program demonstrates the rendering of SVG graphic content in a Firefox 1.5 client browser that is produced by a servlet that is accessed by the browser.
Very similar to the previous program
Except for the fact that this program is a servlet that generates XHTML code as its output, and the previous program named Svg03 is a desktop application that produces an output file containing XHTML code, the two programs are identical.
This program defines a servlet that first creates a DOM tree describing the graphic image in Figure 1, and then transforms that tree into corresponding SVG XML code for transmission to the client.
Not valid XHTML
The program wraps the SVG graphic code in code that would otherwise be valid XHTML code. However the inclusion of the SVG code prevents the code produced by the servlet from being valid XHTML because the SVG element names are not recognized by XHTML validator programs, such as the program at the following URL:
http://validator.w3.org/file-upload.html
Program testing
For testing, the servlet must be deployed on a servlet-compatible server and then accessed using an SVG-compatible browser, such as Firefox 1.5. On my system, the servlet can be tested by first copying it into the following directory on my Tomcat server:
C:\jakarta-tomcat-5.0.27\webapps\ROOT\WEB-INF\classes
Having copied the servlet onto the server and having started the server running, the servlet can be accessed and the SVG code can be rendered by accessing the servlet in Firefox using the following URL:
http://localhost/servlet/Svg04
What about Internet Explorer
As I mentioned earlier, I have not installed the SVG plug-in for IE6. Accessing the servlet using IE6 (without an SVG plug-in installed) simply causes the browser to request permission to save the output produced by the server in a local file. The file name that is recommended by IE6 is simply Svg04 with no extension.
Therefore, the program was tested using J2SE 5.0, Firefox 1.5.09, and jakarta-tomcat-5.0.27 running as a localhost server under WinXP.
Will discuss in fragments
As usual, I will discuss this program in servlets. However, because the code in this program is almost identical to the code in the earlier program named Svg03, I will only discuss the code that is different between the two. You can view the program named Svg04 in its entirety in Listing 31 near the end of the lesson.
As you will see, the difference between the two programs is confined to two small sections of code.
First section of different code
The first section of code that differs between the programs named Svg03 and Svg04 is shown in Listing 28.
Listing 28. First section of different code.
import javax.servlet.*;
import javax.servlet.http.*;
public class Svg04 extends HttpServlet{
public void doGet(HttpServletRequest req,
HttpServletResponse res)
throws ServletException,
IOException{
//Note: This statement is critical to causing
// Firefox to recognize and render the SVG
// code produced by the servlet.
res.setContentType("image/svg+xml");
|
Additional import directives are required
First, because Svg04 is a servlet program, it must declare the two import directives shown in Listing 28 (or fully qualify the classes contained in those two packages when they are referenced in the program code.).
Extend HttpServlet class
Second, because Svg04 is a servlet program, the class must extend the HttpServlet class.
Define the doGet method
Third, because Svg04 is a servlet program and not a desktop application, the main method in the desktop application named Svg03 must be replaced by the method named doGet shown in Listing 28.
Set the content type
Fourth, the program must set the content type as shown in the last statement in Listing 28.
Last section of different code
The last section of code that differs between the two programs is the single statement shown in Listing 29.
Listing 29. Last section of different code.
//Get an output stream for the output produced by
// the program code.
PrintWriter out = res.getWriter();
|
At this point in the program, the desktop application named Svg03 instantiates a PrintWriter output stream object based on a FileOutputStream object. That causes the output produced by the program to be written into a file named junk.svg.
As you can see in Listing 29, the code required to get an appropriate output stream for a servlet is somewhat simpler. Output that is placed in the output stream by the servlet ends up at the client browser for rendering and display.
Those are the only differences between the two programs
Unless I missed something during my comparison of the two programs, the only differences between the two programs are shown in Listing 28 and Listing 29.
It is fortunate that the differences between the two programs are so few in number and so insignificant in content. Testing and debugging servlets can be a difficult task. However, in this case at least, the program can be developed, tested, and debugged as a stand alone Java application, and then converted to a servlet by making the small number of changes shown in Listing 28 and Listing 29.
That ends the discussion and explanation of the program named Svg04. Once again, you can view the program in its entirety in Listing 31 near the end of the lesson.
Run the programs
I encourage you to copy the code from Listing 30 and Listing 31 into your text editor, compile it, and execute it. Experiment with it, making changes, and observing the results of your changes. Then view the results in Firefox 1.5, or some other suitable SVG rendering engine. Experiment with the code, making changes, and observing the results of your changes.
Above all, enjoy the process. Programming, particularly graphics programming can be fun.
Summary
In this lesson, I taught you how to write Java code to produce XHTML files containing in-line SVG/XML code. I also taught you how to write servlets that produce XHTML output containing in-line SVG/XML code.
An SVG program was developed, tested, and debugged as a stand-alone desktop application that produced an output XHTML file to produce the image shown in Figure 1.
Then that desktop application was converted to a servlet program by making the following five changes to the code in the desktop application:
- Declare the following import directives:
import javax.servlet.*; import javax.servlet.http.*;
- Define the main class to extend the HttpServlet class.
- Replace the signature for the main method in the desktop
application with the signature for the doGet
method in the servlet program:
public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException{ - Execute the following statement to set the content type:
res.setContentType("image/svg+"); - Execute the following statement to get an output stream:
PrintWriter out = res.getWriter();
What's next?
The next lesson in this series will teach you how to write servlets that produce XHTML output containing references to external SVG files. Those SVG files may be created on-the-fly during the execution of the servlet.
Future lessons will teach you how to write servlets that:
- Deal with the following graphics elements:
- path
- text
- image (Deal with bit-mapped images in SVG.)
- use (Create and re-use graphics elements.)
- Use SVG symbols.
- Deal with stroke caps in SVG in comparison with similar caps in Java 2D.
- Use the switch element in SVG.
- Deal with other features of SVG, such as animation.
Complete program listings
Listing 30. Program code for Svg03
/*File Svg03.java
Copyright 2006 R.G.Baldwin
The main purpose of this program is to illustrate the
inclusion of SVG graphics in an XHTML file that can be
rendered in Firefox 1.5.
This program uses an SVG graphic library that has been
significantly improved over the one used in the earlier
program named Svg02. The library now supports the
following basic shapes plus linear and radial gradients
and dozens of attributes and transforms on each shape.
rect
circle
ellipse
line
polyline
polygon
These are all of the basic shapes provided by SVG.
The library also contains some convenience methods for
creating elements, nodes, and blocks of code. These
convenience methods are designed to reduce the labor
required to write programs such as this one.
This program creates a DOM tree describing the SVG code
for a particular graphic image. Then it transforms the
DOM tree into SVG code that is wrapped in a file that
would otherwise be a valid XHTML file. Inclusion of the
SVG code, however, prevents the code from being valid
XHTML because the SVG element names are not recognized
by XHTML validator programs, such as the program at the
following URL:
http://validator.w3.org/file-upload.html
The capability demonstrated in this program is a
precursor to being able to create inline SVG code in a
servlet and to cause that SVG code to be rendered in a
Firefox browser that accesses the servlet.
(As you will see, creating an XHTML file is a good bit
more difficult than creating an SVG file as was done
in the earlier program named Svg02.java.)
The output file produced by this program can be rendered
by loading it directly into Firefox 1.5.
Tested using J2SE 5.0, Firefox v1.5.0.8, and WinXP.
*********************************************************/
import javax.xml.parsers.*;
import org.w3c.dom.*;
import java.io.*;
public class Svg03{
public static void main(String argv[]){
//The following data values will be used to create
// SVG graphics.
int ellipseCenterX = 110;
int ellipseCenterY = 100;
int ellipseWidth = 100;
int ellipseHeight = 40;
int ellipseRotate = 15;//degrees
int circleCenterX = 110;
int circleCenterY = 100;
int circleRadius = 30;
int circleRotate = 15;
int rectCenterX = 110;
int rectCenterY = 200;
int rectWidth = 110;
int rectHeight = 80;
int rectRoundX = 26;
int rectRoundY = 25;
int rotatePolyline = 10;
try{
//Create a DOM tree that describes a particular
// graphic image.
//Begin by creating a Document object and a root
// node named svg. All of the graphic content will
// be contained in the svg element. This code was
// explained in the earlier program named
// Xslt01.java that was explained in lesson 2202
// named "Getting Started with Java JAXP and XSL
// Transformations (XSLT)".
//At this point, the program starts using the
// SVG graphics library encapsulated in the class
// named SvgGraphics.
Document document = SvgGraphics.getDocument();
//Create the root node named svg and append it to
// the document.
//Set some attributes on the root node that are
// required for proper rendering.
Element svg = SvgGraphics.makeNode(
document,
null,//parent could be null
"svg",//node type
new String[]{"xmlns","http://www.w3.org/2000/svg",
"version","1.1",
"width","220px",
"height","440px",
"position","static",
"top","0",
"left","0"
});//end call to makeNode
//Show outline of canvas using 'rect' element
Element outline = SvgGraphics.makeNode(
document,
svg,//parent could be null
"rect",//node type
new String[]{"x","0",
"y","0",
"width","220",
"height","440",
"fill","none",
"stroke","black",
"stroke-width","1"
});//end call to makeNode
//Create a node named defs, which will be the parent
// for three gradient definitions. There will be
// two linear gradients and one radial gradient.
// Pass null for the reference to the object for the
// special case where the node has no attributes.
Element defs = SvgGraphics.makeNode(document,
svg,//parent
"defs",
null);
//Create nodes that define three different gradient
// coloring schemes.
//The definitions are identified as gradientA,
// gradientB, and gradientC. They will be referred
// to later to specify the fill colors for an
// ellipse, a circle, and a rounded rectangle.
//Define gradientA, which provides a linear
// gradient from yellow to red to blue going from
// left to right.
Element gradientA = SvgGraphics.makeLinearGradient(
document, //this document
defs, //parent
"gradientA");//id
//Create three stop nodes that identify the colors
// used to produce the gradient and specify where
// the colors begin and end,
SvgGraphics.makeGradientStop(document,
gradientA,//parent
"2%", //start here
"yellow");//color
SvgGraphics.makeGradientStop(document,
gradientA,
"50%",
"red");
SvgGraphics.makeGradientStop(document,
gradientA,
"98%",
"blue");
//Define gradientB, which provides a linear
// gradient having two stops from green to blue
// going from left to right.
Element gradientB =SvgGraphics.makeLinearGradient(
document, //this document
defs, //parent
"gradientB");//id
SvgGraphics.makeGradientStop(document,
gradientB,//parent
"0%", //start here
"green"); //color
SvgGraphics.makeGradientStop(document,
gradientB,
"100%",
"blue");
//Define gradientC, which provides a radial
// gradient from yellow to red to blue going from
// the center to the outer edge.
Element gradientC =
SvgGraphics.makeRadialGradient(
document, //this document
defs, //parent
"gradientC", //ID
"userSpaceOnUse",
rectCenterX, //cx
rectCenterY, //cy
rectWidth/2); //r
SvgGraphics.makeGradientStop(document,
gradientC,
"0%",
"yellow");
SvgGraphics.makeGradientStop(document,
gradientC,
"50%",
"red");
SvgGraphics.makeGradientStop(document,
gradientC,
"100%",
"blue");
//Create a node named g, which will be the parent
// for an ellipse,a circle, a rectangle, a line,
// a polyline, and a polygon. Pass null for the
// reference to the object for the special case
// where the node has no attributes.
Element g = SvgGraphics.makeNode(document,
svg,//parent
"g",
null);
//Create an ellipse with a blue border that is two
// pixels wide. Fill it with the yellow-red-blue
// gradient defined by gradientA.
Element theEllipse = SvgGraphics.makeEllipse(
document,
g,//Owner
ellipseCenterX,
ellipseCenterY,
ellipseWidth,
ellipseHeight);
//Set the appearance of the ellipse.
theEllipse.setAttribute("fill","url(#gradientA)");
theEllipse.setAttribute("stroke","blue");
theEllipse.setAttribute("stroke-width","2");
//Rotate the ellipse by 15-degrees clockwise about
// its center.
theEllipse.setAttribute("transform",
"translate(" + ellipseCenterX + ","
+ ellipseCenterY + ") "
+ "rotate(" + ellipseRotate + ") "
+ "translate(" + (-ellipseCenterX) + ","
+ (-ellipseCenterY) + ") ");
//Position a circle so that it appears to be inside
// the ellipse. Fill it with the green-blue
// gradient defined by gradientB.
Element theCircle = SvgGraphics.makeCircle(
document,
g,//Owner
circleCenterX,
circleCenterY,
circleRadius);
//Set the appearance of the circle and rotate it by
// 15-degrees clockwise about its center.
theCircle.setAttribute("fill","url(#gradientB)");
theCircle.setAttribute("transform",
"translate(" + circleCenterX + ","
+ circleCenterY + ") "
+ "rotate(" + circleRotate + ") "
+ "translate(" + (-circleCenterX) + ","
+ (-circleCenterY) + ") ");
//Make a rounded rectangle and fill it with
// gradientC
Element theRect = SvgGraphics.makeRect(
document,
g,//Owner
rectCenterX - rectWidth/2,//x
rectCenterY - rectHeight/2,//y
rectWidth,
rectHeight);
theRect.setAttribute("fill","url(#gradientC)");
//Round the corners.
theRect.setAttribute("rx",""+ rectRoundX);
theRect.setAttribute("ry",""+ rectRoundY);
//Draw a polyline with four points.
int[] polylinePoints =
{10,235,210,235,110,275,110,225};
Element polyline = SvgGraphics.makePolyline(
document,
g,//owner
polylinePoints);
//Rotate the polyline by 10 degrees around the first
// point.
polyline.setAttribute("transform",
"translate(" + polylinePoints[0] + ","
+ polylinePoints[1] + ")"
+ "rotate(" + rotatePolyline + ")"
+ "translate(" + (-polylinePoints[0])
+ "," + (-polylinePoints[1]) + ")");
//Draw a polygon with four points. Give it a red
// border and fill it with green.
int[] polygonPoints =
{10,335,210,335,110,375,110,325};
Element polygon = SvgGraphics.makePolygon(
document,
g,//parent
polygonPoints);
polygon.setAttribute("fill","green");
polygon.setAttribute("stroke","red");
polygon.setAttribute("stroke-width","3");
//Draw a green line 12 pixels wide. Make the line
// 60% opaque, or 40% transparent, whichever you
// prefer.
Element line = SvgGraphics.makeLine(document,
g, //owner
0, //x1
0, //y1
220, //x2
440);//y2
line.setAttribute("stroke","green");
line.setAttribute("stroke-width","12");
line.setAttribute("stroke-opacity","0.6");
//The remaining code in this class is used to
// transform the DOM tree into SVG code and to wrap
// that code in a document that would otherwise be
// a valid XHTML document.
//Instantiate an object of this class
Svg03 thisObj = new Svg03();
//Get an output stream for the output produced by
// the program code.
PrintWriter out = new PrintWriter(
new FileOutputStream("junk.xhtml"));
//Process the DOM tree, beginning with the Document
// node to produce the output.
//The invocation of processDocumentNode starts a
// recursive process that will process the entire
// DOM tree.
thisObj.processDocumentNode(out,document);
}catch(Exception e){
//Note that no effort was made to provide meaningful
// information in the event of an exception or
// error.
e.printStackTrace(System.err);
}//end catch
}// end main()
//----------------------------------------------------//
//This method is used to produce the text required in
// the output at the document level, such as the
// XML declaration. It also produces the top level
// element tags.
void processDocumentNode(PrintWriter out,Node node){
//Create the beginning of the XHTML document.
out.println("<?xml version=\"1.0\" "
+ "encoding=\"UTF-8\"?>");
out.println(
"<!DOCTYPE html PUBLIC \"-//W3C//DTD "
+ "XHTML 1.0 Transitional//EN\" "
+ "\"http://www.w3.org/TR/xhtml1/"
+ "DTD/xhtml1-transitional.dtd\">");
out.println(SvgGraphics.makeElement(false,"html",
new String[]{"xmlns","http://www.w3.org/1999/xhtml",
"xml:lang","en",
"name","lang"
})//end call to makeElement
);//end println
out.println("<head>");
out.println(SvgGraphics.makeElement(
true,
"meta",
new String[]{"http-equiv","content-type",
"content",
"image/svg+xml; charset=UTF-8"
})//end call to makeElement
);//end println
out.println("<title>Generated XHTML file</title>");
out.println("</head>");
out.println(SvgGraphics.makeElement(
false,
"body",
new String[]{"id","body",
"style","position:absolute;"
+ "z-index:0;"
+ "border:1px solid black;"
+ "left:0;"
+ "top:0;"
+ "width:220px;"
+ "height:440px;"
})//end call to makeElement
);//end println
//Go process the root (document) node. This method
// call triggers a recursive process that will
//process the entire DOM tree.
processNode(out,node);
//The entire DOM tree has been processed when control
// returns to this point.
//Now finish the output document and flush the output
// buffer.
out.println("</body></html>");
out.flush();
}//end processDocumentNode
//----------------------------------------------------//
//There are seven kinds of nodes and most of them were
// handled in the earlier program from which this
// program was derived:
// root or document
// element
// attribute
// text
// comment
// processing instruction
// namespace
//However, because the nodes in this program are
// produced by the program and it is known in advance
// the types of nodes that will be encountered, this
// program is less general than the earlier program.
// Therefore, this method only handles element nodes
// and document nodes.
void processNode(PrintWriter out,Node node){
try{
if (node == null){
System.out.println("Nothing to do, node is null");
return;
}//end if
//Process the incoming node based on its type.
int type = node.getNodeType();
switch(type){
case Node.ELEMENT_NODE:{
//Start creating the start tag.
String nodeName = node.getNodeName();
out.print("<" + nodeName + " ");
//Now add each attribute to the start tag.
NamedNodeMap theList = node.getAttributes();
int length = theList.getLength();
for(int cnt = 0;cnt < length;cnt++){
Node item = theList.item(cnt);
out.print(item.getNodeName() + "=\""
+ item.getNodeValue() + "\" ");
}//end for loop
out.println(">");//close the start tag.
//Process all XML child nodes recursively.
processChildNodes(out,node);
//The element and all of its children have been
// processed when control returns to this point.
//Create the end tag for the element.
out.println("</" + nodeName + ">");
break;
}//end case ELEMENT_NODE
case Node.DOCUMENT_NODE:{
//No action is required other than to process
// the child nodes recursively.
processChildNodes(out,node);
break;
}//end case DOCUMENT_NODE
}//end switch
}catch(Exception e){
e.printStackTrace(System.err);
}//end catch
}//end processNode(Node)
//----------------------------------------------------//
//This method is called to recursively process
// the child nodes belonging to a specified
// node.
void processChildNodes(PrintWriter out,Node node){
NodeList children = node.getChildNodes();
if (children != null){
int len = children.getLength();
//Iterate on NodeList of child nodes.
for(int i = 0; i < len; i++){
//This is a recursive call.
processNode(out,children.item(i));
}//end for loop
}//end if children != null
}//end processChildNodes
//----------------------------------------------------//
}//End class Svg03
//======================================================//
//This is a proof-of-concept graphics class that
// provides method calls for the creation of the following
// DOM tree nodes:
// A general node of any type
// A linear gradient element.
// A radial gradient element.
// An ellipse.
// A circle.
// A rectangle.
// A line.
// A polyline.
// A polygon.
//Each method receives a reference to the overall document
// along with a reference to the parent for the new node.
//When the method returns, the new node has been appended
// to the parent node.
class SvgGraphics{
//----------------------------------------------------//
//This method creates a linear gradient node to which
// stop elements must be appended.
static Element makeLinearGradient(Document document,
Element parent,
String id){
Element gradient =
(Element)document.createElement("linearGradient");
parent.appendChild(gradient);
gradient.setAttribute("id",id);
return gradient;
}//End makeLinearGradient
//----------------------------------------------------//
//This method creates a radial gradient node to which
// stop elements must be appended. Note that numeric
// attributes are set as type String.
static Element makeRadialGradient(Document document,
Element parent,
String id,
String gradientUnits,
int cx,
int cy,
int r){
Element gradient =
(Element)document.createElement("radialGradient");
parent.appendChild(gradient);
gradient.setAttribute("id",id);
gradient.setAttribute("gradientUnits",gradientUnits);
gradient.setAttribute("cx",""+cx);
gradient.setAttribute("cy",""+cy);
gradient.setAttribute("r",""+r);
return gradient;
}//End makeRadialGradient
//----------------------------------------------------//
//This method creates a gradient stop node to be
// appended to a linear gradient node or a radial
// gradient node.
static Element makeGradientStop(Document document,
Element parent,
String location,
String color){
Element stopElement =
(Element)document.createElement("stop");
parent.appendChild(stopElement);
stopElement.setAttribute("offset",location);
stopElement.setAttribute("stop-color",color);
return stopElement;
}//End makeGradientStop
//----------------------------------------------------//
//This method returns a reference to an ellipse. The
// xCoor and yCoor parameters specify the center of the
// ellipse. The xRadius and yRadius parameters specify
// the width and height of the ellipse respectively
// while it is in the horizontal plane before being
// rotated. Numeric attributes are set at type String.
static Element makeEllipse(Document document,
Element parent,
int xCoor,
int yCoor,
int xRadius,
int yRadius){
Element ellipse =
(Element)document.createElement("ellipse");
parent.appendChild(ellipse);
ellipse.setAttribute("cx",""+xCoor);
ellipse.setAttribute("cy",""+yCoor);
ellipse.setAttribute("rx",""+xRadius);
ellipse.setAttribute("ry",""+yRadius);
return ellipse;
}//end makeEllipse
//----------------------------------------------------//
//This method returns a reference to a circle. The
// xCoor and yCoor parameters specify the center of the
// circle. The radius parameter specifies the radus of
// the circle. Numeric attributes are set as type
// String.
static Element makeCircle(Document document,
Element parent,
int xCoor,
int yCoor,
int radius){
Element circle =
(Element)document.createElement("circle");
parent.appendChild(circle);
circle.setAttribute("cx",""+xCoor);
circle.setAttribute("cy",""+yCoor);
circle.setAttribute("r",""+radius);
return circle;
}//end makeCircle
//----------------------------------------------------//
//This method returns a reference to a rectangle. The
// xCoor and yCoor parameters specify the location of
// the upper left corner. The width and height
// parameters specify the width and the height while
// the rectangle is in the horizontal plane before
// being rotated. Numeric attributes are set as type
// String.
static Element makeRect(Document document,
Element parent,
int xCoor,
int yCoor,
int width,
int height){
Element rect =
(Element)document.createElement("rect");
parent.appendChild(rect);
rect.setAttribute("x",""+xCoor);
rect.setAttribute("y",""+yCoor);
rect.setAttribute("width",""+width);
rect.setAttribute("height",""+height);
return rect;
}//end makeRect
//----------------------------------------------------//
//This method returns a reference to a line. x1 and y1
// specify the starting point of the line before it is
// rotated. x2 and y2 specify the end point. By
// default, the stroke is set to black one pixel wide.
// This can be overridden to speciy other colors and
// other widths if you need to do so.
static Element makeLine(Document document,
Element parent,
int x1,
int y1,
int x2,
int y2){
Element line =
(Element)document.createElement("line");
parent.appendChild(line);
line.setAttribute("x1",""+x1);
line.setAttribute("y1",""+y1);
line.setAttribute("x2",""+x2);
line.setAttribute("y2",""+y2);
line.setAttribute("stroke","black");
line.setAttribute("stroke-width","1");
return line;
}//end makeLine
//----------------------------------------------------//
//This method returns a reference to a polyline. The
// array of type int[] must contain an even number of
// values for things to work correctly.
//The values are extracted from the array and treated
// as coordinate values x1,y1, x2,y2, x3,y3 ... etc.
// By default, the stroke is set to black one pixel
// wide with no fill. This can be overridden to other
// colors and other widths if you need to do so.
static Element makePolyline(Document document,
Element parent,
int[] points){
Element polyline =
(Element)document.createElement("polyline");
parent.appendChild(polyline);
String dataPoints = "";
for(int cnt=0;cnt<points.length;cnt++){
dataPoints += "" + points[cnt] + ",";
}//end for loop
polyline.setAttribute("points",dataPoints);
polyline.setAttribute("stroke","black");
polyline.setAttribute("stroke-width","1");
polyline.setAttribute("fill","none");
return polyline;
}//end makePolyline
//----------------------------------------------------//
//This method returns a reference to a polygon. The
// array of type int[] must contain an even number of
// values for things to work correctly.
//The values are extracted from the array and treated
// as coordinate values x1,y1, x2,y2, x3,y3 ... etc.
// By default, the stroke is set to black, one pixel
// wide with no fill. This can be overridden to other
// colors and other widths if you need to do so.
//The major difference between a polygon and a polyline
// is that a polyline leaves the last point dangling.
// However, a polygon automatically draws a line from
// the last point back to the first point to close
// the polygon.
static Element makePolygon(Document document,
Element parent,
int[] points){
Element polygon =
(Element)document.createElement("polygon");
parent.appendChild(polygon);
String dataPoints = "";
for(int cnt=0;cnt<points.length;cnt++){
dataPoints += "" + points[cnt] + ",";
}//end for loop
polygon.setAttribute("points",dataPoints);
polygon.setAttribute("stroke","black");
polygon.setAttribute("stroke-width","1");
polygon.setAttribute("fill","none");
return polygon;
}//end makePolygon
//----------------------------------------------------//
/*
One of the most frustrating things about using Java
to create elements in XML, XHTML, or HTML is having
to deal with the escape characters for the many
required quotation marks. This method constructs an
element, which may or may not have attributes. Also,
the element may or may not be empty.
The user of this method does not have to deal with the
required quotation marks surrounding attribute values
and the corresponding escape characters
The first incoming parameter must be true if the
element is empty and false if the element is not
empty.
If the first parameter is true, the element is sealed
off in the required manner for an empty element. If
the first parameter is false, the method returns the
complete start tag for the element but does not
return a complete element. It is the responsibility
of the calling method to provide the content and the
end tag for the element.
The second parameter to the method must be a String
that specifies the name of the element.
The third parameter to the method must be a reference
to an array object of type String. This array must
contain an even number of elements. Each pair of
elements constitutes the name and the value of an
attribute, in the order name, value, name, value, etc.
If the reference to the array object is null and the
first parameter is false, the method returns the start
tag for an element that has no attributes and is not
empty.
If the reference is null and the first parameter is
true, the method returns a complete empty element with
no attributes (which probably doesn't make any sense).
An example of the recommended usage of the method
follows:
String newElement = SvgGraphics.makeElement(
true/false,
name,
new String[]{"name","value",
"name","value",
"name","value",
"name","value",
"name","value",
"name","value",
"name","value",
"name","value",
"name","value"
});//end call to makeElement
*/
static String makeElement(
boolean empty,String elementName,String[] data){
//Begin constructing the start tag.
String element = "<" + elementName + " ";
//Deal with elements that have no attributes.
if((empty==false) & & (data == null)){
//Return a complete start tag.
return element + ">";
}else if((empty==true) & & (data == null)){
//Return a complete empty element.
return element + "/>";
}//end if
for(int cnt=0;cnt<data.length;cnt+=2){
String name = data[cnt];
String value = data[cnt+1];
element += name + "=" + "\"" + value + "\" ";
}//end for loop
if(empty){
//Terminate the element appropriately for an
// empty element. A complete empty element will
// be returned.
element += "/>";
}else{
//End the start tag for an element that is not
// empty. In this case, only the start tag will
// be returned. The calling program must provide
// the content for the element as well as the end
// tag for the element.
element += ">";
}//end else
return element;
}//end makeElement
//----------------------------------------------------//
/*
The purpose of this method is to create a general node
having any name, and any number of attributes with any
attribute names and any String values for the
attributes, or no attributes at all.
The first parameter is a reference to the document to
which the new node belongs.
The second parameter is a reference to the parent node
to which this node is to be appended so as to become a
child of that node. If this parameter is null, the new
node is appended to the document. Otherwise, it is
appended to the specified parent node.
The third parameter is a String that specifies the type
of node.
The fourth parameter to the method must be a reference
to an array object of type String. This array must
contain an even number of elements. Each pair of
elements constitutes the name and the value of an
attribute, in the order name, value, name, value, etc.
An example of the recommended usage of the method
follows:
Element abc = SvgGraphics.makeNode(
document,
def,//parent could be null
"ghi",//node type
new String[]{"name","value",
"name","value",
"name","value",
"name","value",
"name","value",
"name","value",
"name","value",
"name","value",
"name","value"
});//end call to makeNode
*/
static Element makeNode(Document document,
Element parent,
String nodeType,
String[] data){
Element element =
(Element)document.createElement(nodeType);
if(parent == null){
//For the special case of parent equal to null,
// append the new node to the document.
document.appendChild(element);
}else{
//Otherwise, append the new node to the specified
// parent.
parent.appendChild(element);
}//end else
//Deal with elements that have no attributes.
if(data == null){
return element;
}//end if
for(int cnt=0;cnt<data.length;cnt+=2){
String name = data[cnt];
String value = data[cnt+1];
element.setAttribute(name,value);
}//end for loop
return element;
}//end makeNode
//----------------------------------------------------//
//This is a utility method that is used to execute code
// that is the same regardless of the graphic image
// being produced.
static Document getDocument(){
Document document = null;
try{
DocumentBuilderFactory factory =
DocumentBuilderFactory.newInstance();
DocumentBuilder builder =
factory.newDocumentBuilder();
document = builder.newDocument();
document.setXmlStandalone(false);
}catch(Exception e){
e.printStackTrace(System.err);
System.exit(0);
}//end catch
return document;
}//end getDocument
//----------------------------------------------------//
}//end class SvgGraphics
|
Listing 31. Program code for Svg04.
/*File Svg04.java,
Copyright 2006, R.G.Baldwin
This program demonstrates the rendering of graphic content
on a Firefox 1.5 client by way of the generation of SVG
code in a servlet on the server.
Except for the fact that this program is a servlet that
generates XHTML code in its output, and the earlier
program named Svg03 is a desktop application that produces
an output file containing XHTML code, the two programs
are identical.
This program uses an SVG graphic library that has been
significantly improved over the one used in the earlier
program named Svg02. The library now supports the
following basic shapes plus linear and radial gradients
and dozens of attributes on each shape.
rect
circle
ellipse
line
polyline
polygon
These are all of the basic shapes provided by SVG.
The program defines a servlet that first creates a DOM
tree describing a specific graphic image, and then
transforms that tree into corresponding SVG XML code for
transmission to the client. The program wraps the SVG
graphic code in code that would otherwise be valid XHTML
code. Inclusion of the SVG code, however, prevents the
code from being valid XHTML because the SVG element names
are not recognized by XHTML validator programs, such as
the program at the following URL:
http://validator.w3.org/file-upload.html
This program can only be tested by deploying the servlet
on a servlet-compatible server and then accessing it
using an SVG-compatible browser, such as Firefox 1.5.
On my system, the servlet can be executed by copying it
into the following directory on the Tomcat server and
accessing it from Firefox using the URL shown below:
C:\jakarta-tomcat-5.0.27\webapps\ROOT\WEB-INF\classes
http://localhost/servlet/Svg04
Accessing the servlet using IE 6 (without an SVG plug-in
installed) simply causes the browser to request permission
to save the output produced by the server in a local file.
The file name that is recommended by the browser is simply
Svg04 with no extension.
Tested using J2SE 5.0, Firefox 1.5.08, and
jakarta-tomcat-5.0.27 running as a localhost server under
WinXP.
*********************************************************/
import javax.xml.parsers.*;
import org.w3c.dom.*;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class Svg04 extends HttpServlet{
public void doGet(HttpServletRequest req,
HttpServletResponse res)
throws ServletException,
IOException{
//Note: This statement is critical to causing
// Firefox to recognize and render the SVG
// code produced by the servlet.
res.setContentType("image/svg+xml");
//The following data values will be used to create
// SVG graphics.
int ellipseCenterX = 110;
int ellipseCenterY = 100;
int ellipseWidth = 100;
int ellipseHeight = 40;
int ellipseRotate = 15;//degrees
int circleCenterX = 110;
int circleCenterY = 100;
int circleRadius = 30;
int circleRotate = 15;
int rectCenterX = 110;
int rectCenterY = 200;
int rectWidth = 110;
int rectHeight = 80;
int rectRoundX = 26;
int rectRoundY = 25;
int rotatePolyline = 10;
try{
//Create a DOM tree that describes a particular
// graphic image.
//Begin by creating a Document object and a root
// node named svg. All of the graphic content will
// be contained in the svg element. This code was
// explained in the earlier program named
// Xslt01.java that was explained inlesson 2202
// named "Getting Started with Java JAXP and XSL
// Transformations (XSLT)".
//At this point, the program starts using the
// SVG graphics library encapsulated in the class
// named SvgGraphics.
Document document = SvgGraphics.getDocument();
//Create the root node named svg and append it to
// the document.
//Set some attributes on the root node that are
// required for proper rendering.
Element svg = SvgGraphics.makeNode(
document,
null,//parent could be null
"svg",//node type
new String[]{"xmlns","http://www.w3.org/2000/svg",
"version","1.1",
"width","220px",
"height","440px",
"position","static",
"top","0",
"left","0"
});//end call to makeNode
//Show outline of canvas using 'rect' element
Element outline = SvgGraphics.makeNode(
document,
svg,//parent could be null
"rect",//node type
new String[]{"x","0",
"y","0",
"width","220",
"height","440",
"fill","none",
"stroke","black",
"stroke-width","1"
});//end call to makeNode
//Create a node named defs, which will be the parent
// for three gradient definitions. There will be
// two linear gradients and one radial gradient.
// Pass null for the reference to the object for the
// special case where the node has no attribtes.
Element defs = SvgGraphics.makeNode(document,
svg,//parent
"defs",
null);
//Create nodes that define three different gradient
// coloring schemes.
//The definitions are identified as gradientA,
// gradientB, and gradientC. They will be referred
// to later to specify the fill colors for an
// ellipse, a circle, and a rounded rectangle.
//Define gradientA, which provides a linear
// gradient from yellow to red to blue going from
// left to right.
Element gradientA = SvgGraphics.makeLinearGradient(
document, //this document
defs, //parent
"gradientA");//id
//Create three stop nodes that identify the colors
// used to produce the gradient and specify where
// the colors begin and end,
SvgGraphics.makeGradientStop(document,
gradientA,//parent
"2%", //start here
"yellow");//color
SvgGraphics.makeGradientStop(document,
gradientA,
"50%",
"red");
SvgGraphics.makeGradientStop(document,
gradientA,
"98%",
"blue");
//Define gradientB, which provides a linear
// gradient having two stops from green to blue
// going from left to right.
Element gradientB =SvgGraphics.makeLinearGradient(
document, //this document
defs, //parent
"gradientB");//id
SvgGraphics.makeGradientStop(document,
gradientB,//parent
"0%", //start here
"green"); //color
SvgGraphics.makeGradientStop(document,
gradientB,
"100%",
"blue");
//Define gradientC, which provides a radial
// gradient from yellow to red to blue going from
// the center to the outer edge.
Element gradientC =
SvgGraphics.makeRadialGradient(
document, //this document
defs, //parent
"gradientC", //ID
"userSpaceOnUse",
rectCenterX, //cx
rectCenterY, //cy
rectWidth/2); //r
SvgGraphics.makeGradientStop(document,
gradientC,
"0%",
"yellow");
SvgGraphics.makeGradientStop(document,
gradientC,
"50%",
"red");
SvgGraphics.makeGradientStop(document,
gradientC,
"100%",
"blue");
//Create a node named g, which will be the parent
// for an ellipse,a circle, a rectangle, a line,
// a polyline, and a polygon. Pass null for the
// reference to the object for the special case
// where the node has no attribtes.
Element g = SvgGraphics.makeNode(document,
svg,//parent
"g",
null);
//Create an ellipse with a blue border that is two
// pixels wide. Fill it with the yellow-red-blue
// gradient defined by gradientA.
Element theEllipse = SvgGraphics.makeEllipse(
document,
g,//Owner
ellipseCenterX,
ellipseCenterY,
ellipseWidth,
ellipseHeight);
//Set the appearance of the ellipse.
theEllipse.setAttribute("fill","url(#gradientA)");
theEllipse.setAttribute("stroke","blue");
theEllipse.setAttribute("stroke-width","2");
//Rotate the ellipse by 15-degrees clockwise about
// its center.
theEllipse.setAttribute("transform",
"translate(" + ellipseCenterX + ","
+ ellipseCenterY + ") "
+ "rotate(" + ellipseRotate + ") "
+ "translate(" + (-ellipseCenterX) + ","
+ (-ellipseCenterY) + ") ");
//Position a circle so that it appears to be inside
// the ellipse. Fill it with the green-blue
// gradient defined by gradientB.
Element theCircle = SvgGraphics.makeCircle(
document,
g,//Owner
circleCenterX,
circleCenterY,
circleRadius);
//Set the appearance of the circle and rotate it by
// 15-degrees clockwise about its center.
theCircle.setAttribute("fill","url(#gradientB)");
theCircle.setAttribute("transform",
"translate(" + circleCenterX + ","
+ circleCenterY + ") "
+ "rotate(" + circleRotate + ") "
+ "translate(" + (-circleCenterX) + ","
+ (-circleCenterY) + ") ");
//Make a rounded rectangle and fill it with
// gradientC
Element theRect = SvgGraphics.makeRect(
document,
g,//Owner
rectCenterX - rectWidth/2,//x
rectCenterY - rectHeight/2,//y
rectWidth,
rectHeight);
theRect.setAttribute("fill","url(#gradientC)");
//Round the corners.
theRect.setAttribute("rx",""+ rectRoundX);
theRect.setAttribute("ry",""+ rectRoundY);
//Draw a polyline with four points.
int[] polylinePoints =
{10,235,210,235,110,275,110,225};
Element polyline = SvgGraphics.makePolyline(
document,
g,//owner
polylinePoints);
//Rotate the polyline by 10 degrees around the first
// point.
polyline.setAttribute("transform",
"translate(" + polylinePoints[0] + ","
+ polylinePoints[1] + ")"
+ "rotate(" + rotatePolyline + ")"
+ "translate(" + (-polylinePoints[0])
+ "," + (-polylinePoints[1]) + ")");
//Draw a polygon with four points. Give it a red
// border and fill it with green.
int[] polygonPoints =
{10,335,210,335,110,375,110,325};
Element polygon = SvgGraphics.makePolygon(
document,
g,//parent
polygonPoints);
polygon.setAttribute("fill","green");
polygon.setAttribute("stroke","red");
polygon.setAttribute("stroke-width","3");
//Draw a green line 12 pixels wide. Make the line
// 60% opaque, or 40% transparent, whichever you
// prefer.
Element line = SvgGraphics.makeLine(document,
g, //owner
0, //x1
0, //y1
220, //x2
440);//y2
line.setAttribute("stroke","green");
line.setAttribute("stroke-width","12");
line.setAttribute("stroke-opacity","0.6");
//The remaining code in this class is used to
// transform the DOM tree into SVG code and to wrap
// that code in a document that would otherwise be
// a valid XHTML document.
//Instantiate an object of this class
Svg04 thisObj = new Svg04();
//Get an output stream for the output produced by
// the program code.
PrintWriter out = res.getWriter();
//Process the DOM tree, beginning with the Document
// node to produce the output.
//The invocation of processDocumentNode starts a
// recursive process that will process the entire
// DOM tree.
thisObj.processDocumentNode(out,document);
}catch(Exception e){
//Note that no effort was made to provide meaningful
// information in the event of an exception or
// error.
e.printStackTrace(System.err);
}//end catch
}//end doGet()
//----------------------------------------------------//
//This method is used to produce the text required in
// the output at the document level, such as the
// XML declaration. It also produces the top level
// element tags.
void processDocumentNode(PrintWriter out,Node node){
//Create the beginning of the XHTML document.
out.println("<?xml version=\"1.0\" "
+ "encoding=\"UTF-8\"?>");
out.println(
"<!DOCTYPE html PUBLIC \"-//W3C//DTD "
+ "XHTML 1.0 Transitional//EN\" "
+ "\"http://www.w3.org/TR/xhtml1/"
+ "DTD/xhtml1-transitional.dtd\">");
out.println(SvgGraphics.makeElement(false,"html",
new String[]{"xmlns","http://www.w3.org/1999/xhtml",
"xml:lang","en",
"name","lang"
})//end call to makeElement
);//end println
out.println("<head>");
out.println(SvgGraphics.makeElement(
true,
"meta",
new String[]{"http-equiv","content-type",
"content",
"image/svg+xml; charset=UTF-8"
})//end call to makeElement
);//end println
out.println("<title>Generated XHTML file</title>");
out.println("</head>");
out.println(SvgGraphics.makeElement(
false,
"body",
new String[]{"id","body",
"style","position:absolute;"
+ "z-index:0;"
+ "border:1px solid black;"
+ "left:0;"
+ "top:0;"
+ "width:220px;"
+ "height:440px;"
})//end call to makeElement
);//end println
//Go process the root (document) node. This method
// call triggers a recursive process that will
//process the entire DOM tree.
processNode(out,node);
//The entire DOM tree has been processed when control
// returns to this point.
//Now finish the output document and flush the output
// buffer.
out.println("</body></html>");
out.flush();
}//end processDocumentNode
//----------------------------------------------------//
//There are seven kinds of nodes and most of them were
// handled in the earlier program from which this
// program was derived:
// root or document
// element
// attribute
// text
// comment
// processing instruction
// namespace
//However, because the nodes in this program are
// produced by the program and it is known in advance
// the types of nodes that will be encountered, this
// program is less general than the earlier program.
// Therefore, this method only handles element nodes
// and document nodes..
void processNode(PrintWriter out,Node node){
try{
if (node == null){
System.out.println("Nothing to do, node is null");
return;
}//end if
//Process the incoming node based on its type.
int type = node.getNodeType();
switch(type){
case Node.ELEMENT_NODE:{
//Start creating the start tag.
String nodeName = node.getNodeName();
out.print("<" + nodeName + " ");
//Now add each attribute to the start tag.
NamedNodeMap theList = node.getAttributes();
int length = theList.getLength();
for(int cnt = 0;cnt < length;cnt++){
Node item = theList.item(cnt);
out.print(item.getNodeName() + "=\""
+ item.getNodeValue() + "\" ");
}//end for loop
out.println(">");//close the start tag.
//Process all XML child nodes recursively.
processChildNodes(out,node);
//The element and all of its children have been
// processed when control returns to this point.
//Create the end tag for the element.
out.println("</" + nodeName + ">");
break;
}//end case ELEMENT_NODE
case Node.DOCUMENT_NODE:{
//No action is required other than to process
// the child nodes recursively.
processChildNodes(out,node);
break;
}//end case DOCUMENT_NODE
}//end switch
}catch(Exception e){
e.printStackTrace(System.err);
}//end catch
}//end processNode(Node)
//----------------------------------------------------//
//This method is called to recursively process
// the child nodes belonging to a specified
// node.
void processChildNodes(PrintWriter out,Node node){
NodeList children = node.getChildNodes();
if (children != null){
int len = children.getLength();
//Iterate on NodeList of child nodes.
for(int i = 0; i < len; i++){
//This is a recursive call.
processNode(out,children.item(i));
}//end for loop
}//end if children != null
}//end processChildNodes
//----------------------------------------------------//
}//end class Svg04
//======================================================//
//This is a proof-of-concept graphics class that
// provides method calls for the creation of the following
// DOM tree nodes:
// A general node of any type
// A linear gradient element.
// A radial gradient element.
// An ellipse.
// A circle.
// A rectangle.
// A line.
// A polyline.
// A polygon.
//Each method receives a reference to the overall document
// along with a reference to the parent for the new node.
//When the method returns, the new node has been appended
// to the parent node.
class SvgGraphics{
//----------------------------------------------------//
//This method creates a linear gradient node to which
// stop elements must be appended.
static Element makeLinearGradient(Document document,
Element parent,
String id){
Element gradient =
(Element)document.createElement("linearGradient");
parent.appendChild(gradient);
gradient.setAttribute("id",id);
return gradient;
}//End makeLinearGradient
//----------------------------------------------------//
//This method creates a radial gradient node to which
// stop elements must be appended. Note that numeric
// attributes are set as type String.
static Element makeRadialGradient(Document document,
Element parent,
String id,
String gradientUnits,
int cx,
int cy,
int r){
Element gradient =
(Element)document.createElement("radialGradient");
parent.appendChild(gradient);
gradient.setAttribute("id",id);
gradient.setAttribute("gradientUnits",gradientUnits);
gradient.setAttribute("cx",""+cx);
gradient.setAttribute("cy",""+cy);
gradient.setAttribute("r",""+r);
return gradient;
}//End makeRadialGradient
//----------------------------------------------------//
//This method creates a gradient stop node to be
// appended to a linear gradient node or a radial
// gradient node.
static Element makeGradientStop(Document document,
Element parent,
String location,
String color){
Element stopElement =
(Element)document.createElement("stop");
parent.appendChild(stopElement);
stopElement.setAttribute("offset",location);
stopElement.setAttribute("stop-color",color);
return stopElement;
}//End makeGradientStop
//----------------------------------------------------//
//This method returns a reference to an ellipse. The
// xCoor and yCoor parameters specify the center of the
// ellipse. The xRadius and yRadius parameters specify
// the width and height of the ellipse respectively
// while it is in the horizontal plane before being
// rotated. Numeric attributes are set at type String.
static Element makeEllipse(Document document,
Element parent,
int xCoor,
int yCoor,
int xRadius,
int yRadius){
Element ellipse =
(Element)document.createElement("ellipse");
parent.appendChild(ellipse);
ellipse.setAttribute("cx",""+xCoor);
ellipse.setAttribute("cy",""+yCoor);
ellipse.setAttribute("rx",""+xRadius);
ellipse.setAttribute("ry",""+yRadius);
return ellipse;
}//end makeEllipse
//----------------------------------------------------//
//This method returns a reference to a circle. The
// xCoor and yCoor parameters specify the center of the
// circle. The radius parameter specifies the radus of
// the circle. Numeric attributes are set as type
// String.
static Element makeCircle(Document document,
Element parent,
int xCoor,
int yCoor,
int radius){
Element circle =
(Element)document.createElement("circle");
parent.appendChild(circle);
circle.setAttribute("cx",""+xCoor);
circle.setAttribute("cy",""+yCoor);
circle.setAttribute("r",""+radius);
return circle;
}//end makeCircle
//----------------------------------------------------//
//This method returns a reference to a rectangle. The
// xCoor and yCoor parameters specify the location of
// the upper left corner. The width and height
// parameters specify the width and the height while
// the rectangle is in the horizontal plane before
// being rotated. Numeric attributes are set as type
// String.
static Element makeRect(Document document,
Element parent,
int xCoor,
int yCoor,
int width,
int height){
Element rect =
(Element)document.createElement("rect");
parent.appendChild(rect);
rect.setAttribute("x",""+xCoor);
rect.setAttribute("y",""+yCoor);
rect.setAttribute("width",""+width);
rect.setAttribute("height",""+height);
return rect;
}//end makeRect
//----------------------------------------------------//
//This method returns a reference to a line. x1 and y1
// specify the starting point of the line before it is
// rotated. x2 and y2 specify the end point. By
// default, the stroke is set to black one pixel wide.
// This can be overridden to speciy other colors and
// other widths if you need to do so.
static Element makeLine(Document document,
Element parent,
int x1,
int y1,
int x2,
int y2){
Element line =
(Element)document.createElement("line");
parent.appendChild(line);
line.setAttribute("x1",""+x1);
line.setAttribute("y1",""+y1);
line.setAttribute("x2",""+x2);
line.setAttribute("y2",""+y2);
line.setAttribute("stroke","black");
line.setAttribute("stroke-width","1");
return line;
}//end makeLine
//----------------------------------------------------//
//This method returns a reference to a polyline. The
// array of type int[] must contain an even number of
// values for things to work correctly.
//The values are extracted from the array and treated
// as coordinate values x1,y1, x2,y2, x3,y3 ... etc.
// By default, the stroke is set to black one pixel
// wide with no fill. This can be overridden to other
// colors and other widths if you need to do so.
static Element makePolyline(Document document,
Element parent,
int[] points){
Element polyline =
(Element)document.createElement("polyline");
parent.appendChild(polyline);
String dataPoints = "";
for(int cnt=0;cnt<points.length;cnt++){
dataPoints += "" + points[cnt] + ",";
}//end for loop
polyline.setAttribute("points",dataPoints);
polyline.setAttribute("stroke","black");
polyline.setAttribute("stroke-width","1");
polyline.setAttribute("fill","none");
return polyline;
}//end makePolyline
//----------------------------------------------------//
//This method returns a reference to a polygon. The
// array of type int[] must contain an even number of
// values for things to work correctly.
//The values are extracted from the array and treated
// as coordinate values x1,y1, x2,y2, x3,y3 ... etc.
// By default, the stroke is set to black, one pixel
// wide with no fill. This can be overridden to other
// colors and other widths if you need to do so.
//The major difference between a polygon and a polyline
// is that a polyline leaves the last point dangling.
// However, a polygon automatically draws a line from
// the last point back to the first point to close
// the polygon.
static Element makePolygon(Document document,
Element parent,
int[] points){
Element polygon =
(Element)document.createElement("polygon");
parent.appendChild(polygon);
String dataPoints = "";
for(int cnt=0;cnt<points.length;cnt++){
dataPoints += "" + points[cnt] + ",";
}//end for loop
polygon.setAttribute("points",dataPoints);
polygon.setAttribute("stroke","black");
polygon.setAttribute("stroke-width","1");
polygon.setAttribute("fill","none");
return polygon;
}//end makePolygon
//----------------------------------------------------//
/*
One of the most frustrating things about using Java
to create elements in XML, XHTML, or HTML is having
to deal with the escape characters for the many
required quotation marks. This method constructs an
element, which may or may not have attributes. Also,
the element may or may not be empty.
The user of this method does not have to deal with the
required quotation marks surrounding attribute values
and the corresponding escape characters
The first incoming parameter must be true if the
element is empty and false if the element is not
empty.
If the first parameter is true, the element is sealed
off in the required manner for an empty element. If
the first parameter is false, the method returns the
complete start tag for the element but does not
return a complete element. It is the responsibility
of the calling method to provide the content and the
end tag for the element.
The second parameter to the method must be a String
that specifies the name of the element.
The third parameter to the method must be a reference
to an array object of type String. This array must
contain an even number of elements. Each pair of
elements constitutes the name and the value of an
attribute, in the order name, value, name, value, etc.
If the reference to the array object is null and the
first parameter is false, the method returns the start
tag for an element that has no attributes and is not
empty.
If the reference is null and the first parameter is
true, the method returns a complete empty element with
no attributes (which probably doesn't make any sense).
An example of the recommended usage of the method
follows:
String newElement = SvgGraphics.makeElement(
true/false,
name,
new String[]{"name","value",
"name","value",
"name","value",
"name","value",
"name","value",
"name","value",
"name","value",
"name","value",
"name","value"
});//end call to makeElement
*/
static String makeElement(
boolean empty,String elementName,String[] data){
//Begin constructing the start tag.
String element = "<" + elementName + " ";
//Deal with elements that have no attributes.
if((empty==false) && (data == null)){
//Return a complete start tag.
return element + ">";
}else if((empty==true) && (data == null)){
//Return a complete empty element.
return element + "/>";
}//end if
for(int cnt=0;cnt<data.length;cnt+=2){
String name = data[cnt];
String value = data[cnt+1];
element += name + "=" + "\"" + value + "\" ";
}//end for loop
if(empty){
//Terminate the element appropriately for an
// empty element. A complete empty element will
// be returned.
element += "/>";
}else{
//End the start tag for an element that is not
// empty. In this case, only the start tag will
// be returned. The calling program must provide
// the content for the element as well as the end
// tag for the element.
element += ">";
}//end else
return element;
}//end makeElement
//----------------------------------------------------//
/*
The purpose of this method is to create a general node
having any name, and any number of attributes with any
attribute names and any String values for the
attributes, or no attributes at all.
The first parameter is a reference to the document to
which the new node belongs.
The second parameter is a reference to the parent node
to which this node is to be appended so as to become a
child of that node. If this parameter is null, the new
node is appended to the document. Otherwise, it is
appended to the specified parent node.
The third parameter is a String that specifies the type
of node.
The fourth parameter to the method must be a reference
to an array object of type String. This array must
contain an even number of elements. Each pair of
elements constitutes the name and the value of an
attribute, in the order name, value, name, value, etc.
An example of the recommended usage of the method
follows:
Element abc = SvgGraphics.makeNode(
document,
def,//parent could be null
"ghi",//node type
new String[]{"name","value",
"name","value",
"name","value",
"name","value",
"name","value",
"name","value",
"name","value",
"name","value",
"name","value"
});//end call to makeNode
*/
static Element makeNode(Document document,
Element parent,
String nodeType,
String[] data){
Element element =
(Element)document.createElement(nodeType);
if(parent == null){
//For the special case of parent equal to null,
// append the new node to the document.
document.appendChild(element);
}else{
//Otherwise, append the new node to the specified
// parent.
parent.appendChild(element);
}//end else
//Deal with elements that have no attributes.
if(data == null){
return element;
}//end if
for(int cnt=0;cnt<data.length;cnt+=2){
String name = data[cnt];
String value = data[cnt+1];
element.setAttribute(name,value);
}//end for loop
return element;
}//end makeNode
//----------------------------------------------------//
//This is a utility method that is used to execute code
// that is the same regardless of the graphic image
// being produced.
static Document getDocument(){
Document document = null;
try{
DocumentBuilderFactory factory =
DocumentBuilderFactory.newInstance();
DocumentBuilder builder =
factory.newDocumentBuilder();
document = builder.newDocument();
}catch(Exception e){
e.printStackTrace(System.err);
System.exit(0);
}//end catch
return document;
}//end getDocument
//----------------------------------------------------//
}//end class SvgGraphics&n/pre>
|
Copyright
Copyright 2007, Richard G. Baldwin. Reproduction in whole or in part in any form or medium without express written permission from Richard Baldwin is prohibited.
Resources
Java 2D Graphics
300 Java 2D Graphics,
Nested Top-Level Classes and Interfaces
302 Java 2D Graphics,
The Point2D Class
304 Java 2D Graphics,
The Graphics2D Class
306 Java 2D Graphics,
Simple Affine Transforms
308 Java 2D Graphics,
The Shape Interface, Part 1
310 Java 2D Graphics,
The Shape Interface, Part 2
312 Java 2D Graphics,
Solid Color Fill
314 Java 2D Graphics,
Gradient Color Fill
316 Java 2D Graphics,
Texture Fill
318 Java 2D Graphics,
The Stroke Interface
320 Java 2D Graphics,
The Composite Interface and Transparency
322 Java 2D Graphics,
The Composite Interface, GradientPaint, and Transparency
324 Java 2D Graphics,
The Color Constructors and Transparency
Java 2D API
Specification
Java 2D API
Java API for XML Processing (JAXP)
2200 Java
API for XML Processing (JAXP), Getting Started
2202 Getting
Started with Java JAXP and XSL Transformations (XSLT)
2204 Java
JAXP, Exposing a DOM Tree
2206 Java
JAXP, Implementing Default XSLT Behavior in Java
2208 Java
JAXP, Writing Java Code to Emulate an XSLT Transformation
2210 Java
JAXP, Transforming XML to XHTML
Links to numerous XML tutorials
by Richard G. Baldwin
Scalable Vector Graphics (SVG)
2212 Java JAXP, Creating graphics using Java and SVG
2214 An improved approach for creating SVG/XML code and SVG/XML DOM nodes
using Java
Scalable Vector Graphics (SVG) 1.1
Specification
Adobe SVG Viewer plug-in
Create vector graphics in the browser with SVG by Uche Ogbuji
SVG Tutorial
SVG Basics
Miscellaneous
W3C Markup Validation
Service.
About the author
Richard Baldwin is a college professor (at Austin Community College in Austin, TX) and private consultant whose primary focus is a combination of Java, C#, and XML. In addition to the many platform and/or language independent benefits of Java and C# applications, he believes that a combination of Java, C#, and XML will become the primary driving force in the delivery of structured information on the Web.Richard has participated in numerous consulting projects and he frequently provides onsite training at the high-tech companies located in and around Austin, Texas. He is the author of Baldwin's Programming Tutorials, which have gained a worldwide following among experienced and aspiring programmers. He has also published articles in JavaPro magazine.
In addition to his programming expertise, Richard has many years of practical experience in Digital Signal Processing (DSP). His first job after he earned his Bachelor's degree was doing DSP in the Seismic Research Department of Texas Instruments. (TI is still a world leader in DSP.) In the following years, he applied his programming and DSP expertise to other interesting areas including sonar and underwater acoustics.
Richard holds an MSEE degree from Southern Methodist University and has many years of experience in the application of computer technology to real-world problems.

