Using SVG in Servlets and JavaServer Pages
The Scalable Vector Graphics (SVG) is a language used for creating vector (as opposed to raster) graphics, including shapes, images, and text. Since this format is based on the extensible markup language (XML), it can be extremely useful in web page authoring.
But the problem is that most of the current browsers do not have built-in support for SVG. Currently, this support is added through plug-ins, e.g. the Adobe SVG viewer. I don't know what percentage of internet users have installed an SVG plug-in. So it seems that if we are going to use SVG in our web pages in a practical way, we must rasterize the image before sending the page to the client (though this makes it an ordinary image, not a vector graphic!).
This can be easily done in JSP pages using the library of the Apache Batik project. I have done this and I present the code below. (Please note that the code for this post is released under the GNU General Public License and that there is no warranty as to its correctness or usability.)
Conversion of the SVG source into an image in PNG format may make problems in a case of simultaneous access. So, a noCache
attribute has been devised for the <h:svg>
tag, so that we can turn on/off the conversion. The best way would be to use noCache="true"
while the page is under development, and then turn off conversion with noCache="false"
when our images have taken their permanent form.
The full source of my simple application (svgtest
) is shown below. But please keep in mind that you need the following libraries to get this application running:
- Batik jar file(s) (may be a single or multiple jar files depending on your build target). I have used batik 1.5.
- JSTL (
jstl.jar
,standard.jar
) - Xerces XML parser (
xerces_2_3_0.jar
). This is included with the batik distribution linked above.
Needless to say, the jar files go to the TOMCAT/webapps/svgtest/WEB-INF/lib
directory.
Figure 1. The graphics on this page have been created using SVG. Particularly, the three hiragana and kanji characters comprising the Japanese phrase for "Good day!" (今日は) (konnichi wa in rōmaji) have been rendered graphically to ensure their proper appearance even on machines lacking suitable unicode fonts. |
The source code for this simple application follows.
TOMCAT/webapps/svgtest/index.jspx
<?xml version="1.0" encoding="windows-1256" ?>
<!-- Copyright © 2005, Ghasem Kiani. License: GPL. -->
<jsp:root version="2.0" xmlns="http://www.w3.org/1999/xhtml" xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:fn="http://java.sun.com/jsp/jstl/functions" xmlns:h="urn:jsptagdir:/WEB-INF/tags/html" xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:page="urn:jsptagdir:/WEB-INF/tags/page" xmlns:xlink="http://www.w3.org/1999/xlink">
<jsp:directive.page contentType="text/html" />
<c:set scope="page" value="${true}" var="debug" />
<c:set property="characterEncoding" target="${pageContext.request}" value="UTF-8" />
<c:set property="characterEncoding" target="${pageContext.response}" value="UTF-8" />
<page:simple title="SVG Test">
<div style="direction: rtl; background-color: white;">
<c:set scope="page" value="تصوير" var="text1" />
<p>
<h:img noCacheSvg="${debug}" src="/title.png">
<jsp:attribute name="svg">
<svg height="25mm" width="100mm" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<filter filterUnits="objectBoundingBox" height="1.4" id="dropShadow" width="1.4">
<feGaussianBlur in="SourceAlpha" stdDeviation="4" />
<feOffset dx="4" dy="4" />
<feComponentTransfer result="shadow">
<feFuncA intercept="0" slope="0.5" type="linear" />
</feComponentTransfer>
</filter>
<filter id="emboss">
<feGaussianBlur in="SourceAlpha" result="blur" stdDeviation="2" />
<feSpecularLighting in="blur" kernelUnitLength="1" result="spec" specularConstant="1" specularExponent="16" style="lighting-color:white" surfaceScale="-3">
<feDistantLight azimuth="45" elevation="45" />
</feSpecularLighting>
<feComposite in="spec" in2="SourceGraphic" operator="in" result="specOut" />
</filter>
</defs>
<g style="font-size: 20mm; font-family: b nasim; font-weight: bold; text-anchor: end;">
<text style="filter:url(#dropShadow)" x="95%" y="66.667%">
${text1}
</text>
<text style="fill:rgb(172,20,20)" x="95%" y="66.667%">
${text1}
</text>
<text style="filter:url(#emboss)" x="95%" y="66.667%">
${text1}
</text>
</g>
</svg>
</jsp:attribute>
</h:img>
</p>
<h:roundrect fill="lightblue" id="mytest1" noCache="true" r="25" stroke="orange" strokeWidth="3">
<div style="direction: rtl; margin: 1mm;">
<p>
<strong>
كلماتي كه ممكن است در مرورگر شما درست نمايش داده نشوند، به صورت تصوير نشان داده ميشوند. مانند
<h:img noCacheSvg="${debug}" src="/konnichiwa.png">
<jsp:attribute name="svg">
<svg height="1em" width="3em" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect fill="lightblue" height="100%" stroke-width="0" width="100%" x="0" y="0" />
<text x="0" y="90%">
&#20170;&#26085;&#12399;
</text>
</svg>
</jsp:attribute>
</h:img>
كه معنايش ميشود روز به خير!
</strong>
</p>
</div>
</h:roundrect>
</div>
</page:simple>
</jsp:root>
TOMCAT/webapps/svgtest/WEB-INF/tags/html/svg.tagx
<?xml version="1.0" encoding="windows-1256" ?>
<!-- Copyright © 2005, Ghasem Kiani. License: GPL. -->
<jsp:root version="2.0" xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:jsp="http://java.sun.com/JSP/Page">
<jsp:directive.tag body-content="scriptless" />
<jsp:directive.attribute name="image" required="true" />
<jsp:directive.attribute name="noCache" type="java.lang.Boolean" />
<c:set scope="page" var="svg">
<jsp:doBody />
</c:set>
<jsp:declaration>
<![CDATA[
class PngMaker extends org.apache.batik.transcoder.image.PNGTranscoder
{
public boolean convert(String svgUri, String png)
{
java.io.OutputStream os;
org.apache.batik.bridge.DocumentLoader dl = new org.apache.batik.bridge.DocumentLoader(userAgent);
try
{
org.w3c.dom.svg.SVGDocument doc = (org.w3c.dom.svg.SVGDocument)dl.loadDocument(svgUri);
os = new java.io.BufferedOutputStream(new java.io.FileOutputStream(png));
transcode(doc, null, new org.apache.batik.transcoder.TranscoderOutput(os));
os.flush();
os.close();
return true;
}
catch(Exception e)
{
System.out.println(e.getMessage());
return false;
}
}
}
]]>
</jsp:declaration>
<jsp:scriptlet>
<![CDATA[
String png = application.getRealPath(String.valueOf(getJspContext().getAttribute("image")));
boolean noCache = new Boolean(String.valueOf(getJspContext().getAttribute("noCache"))).booleanValue();
if(noCache || ! new java.io.File(png).exists())
{
java.io.File dir = new java.io.File(application.getRealPath("/WEB-INF/temp"));
if(!dir.exists()) dir.mkdir();
java.io.File f = java.io.File.createTempFile("src", ".svg", dir);
f.deleteOnExit();
java.io.BufferedWriter bw = new java.io.BufferedWriter(new java.io.OutputStreamWriter(new java.io.FileOutputStream(f), "UTF-8"));
bw.write("<?xml version='1.0' encoding='UTF-8' ?>");
bw.write(String.valueOf(getJspContext().getAttribute("svg")));
bw.close();
new PngMaker().convert(f.toURI().toString(), png);
}
]]>
</jsp:scriptlet>
</jsp:root>
TOMCAT/webapps/svgtest/WEB-INF/tags/html/img.tagx
<?xml version="1.0" encoding="windows-1256" ?>
<!-- Copyright © 2005, Ghasem Kiani. License: GPL. -->
<jsp:root version="2.0" xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:fn="http://java.sun.com/jsp/jstl/functions" xmlns:h="urn:jsptagdir:/WEB-INF/tags/html" xmlns:jsp="http://java.sun.com/JSP/Page">
<jsp:directive.tag body-content="scriptless" dynamic-attributes="dyn" />
<jsp:directive.attribute name="src" />
<jsp:directive.attribute name="svg" />
<jsp:directive.attribute name="noCacheSvg" type="java.lang.Boolean" />
<jsp:directive.attribute name="params" type="java.util.Map" />
<jsp:directive.attribute name="alt" />
<jsp:directive.attribute name="title" />
<jsp:directive.attribute name="style" />
<jsp:directive.attribute name="styleId" />
<jsp:directive.attribute name="styleClass" />
<c:url scope="page" value="${src}" var="url" />
<c:if test="${not empty svg}">
<h:svg image="${src}" noCache="${noCacheSvg}">
${svg}
</h:svg>
</c:if>
<img alt="${alt}" class="${styleClass}" id="${styleId}" src="${url}" style="${style}" title="${title}">
<jsp:doBody />
</img>
</jsp:root>
TOMCAT/webapps/svgtest/WEB-INF/tags/html/roundrect.tagx
<?xml version="1.0" encoding="windows-1256" ?>
<!-- Copyright © 2005, Ghasem Kiani. License: GPL. -->
<jsp:root version="2.0" xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:h="urn:jsptagdir:/WEB-INF/tags/html" xmlns:jsp="http://java.sun.com/JSP/Page">
<jsp:directive.tag body-content="scriptless" />
<jsp:directive.attribute name="id" required="true" />
<jsp:directive.attribute name="fill" />
<jsp:directive.attribute name="stroke" />
<jsp:directive.attribute name="strokeWidth" />
<jsp:directive.attribute name="r" />
<jsp:directive.attribute name="width" />
<jsp:directive.attribute name="height" />
<jsp:directive.attribute name="body" />
<jsp:directive.attribute name="noCache" />
<c:if test="${empty fill}">
<c:set scope="page" value="none" var="fill" />
</c:if>
<c:if test="${empty stroke}">
<c:set scope="page" value="black" var="stroke" />
</c:if>
<c:if test="${empty strokeWidth}">
<c:set scope="page" value="1" var="strokeWidth" />
</c:if>
<c:if test="${empty r}">
<c:set scope="page" value="5" var="r" />
</c:if>
<c:if test="${empty width}">
<c:set scope="page" value="auto" var="width" />
</c:if>
<c:if test="${empty height}">
<c:set scope="page" value="auto" var="height" />
</c:if>
<c:if test="${empty body}">
<c:set scope="page" var="body">
<jsp:doBody />
</c:set>
</c:if>
<table cellpadding="0" cellspacing="0" style="direction: ltr; width: ${width}; height: ${height};">
<tr>
<h:svg image="/${id}nw.png" noCache="${noCache}">
<svg height="${r}" width="${r}" xmlns="http://www.w3.org/2000/svg">
<circle cx="100%" cy="100%" fill="${fill}" r="${r/2}" stroke="${stroke}" stroke-width="${strokeWidth}" />
</svg>
</h:svg>
<c:url value="/${id}nw.png" var="bg" />
<td style="width: ${r};">
<h:img src="/${id}nw.png" style="margin: 0; padding: 0; width: ${r}; height: ${r};" />
</td>
<h:svg image="/${id}n.png" noCache="${noCache}">
<svg height="${r}" width="${r}" xmlns="http://www.w3.org/2000/svg">
<rect fill="${fill}" height="50%" stroke="${stroke}" stroke-width="${0}" width="100%" x="0" y="50%" />
<line stroke="${stroke}" stroke-width="${strokeWidth}" x1="0" x2="100%" y1="50%" y2="50%" />
</svg>
</h:svg>
<c:url value="/${id}n.png" var="bg" />
<td style="background: url(${bg});">
&nbsp;
</td>
<h:svg image="/${id}ne.png" noCache="${noCache}">
<svg height="${r}" width="${r}" xmlns="http://www.w3.org/2000/svg">
<circle cx="0" cy="100%" fill="${fill}" r="${r/2}" stroke="${stroke}" stroke-width="${strokeWidth}" />
</svg>
</h:svg>
<c:url value="/${id}ne.png" var="bg" />
<td style="width: ${r};">
<h:img src="/${id}ne.png" style="margin: 0; padding: 0; width: ${r}; height: ${r};" />
</td>
</tr>
<tr>
<h:svg image="/${id}w.png" noCache="${noCache}">
<svg height="${r}" width="${r}" xmlns="http://www.w3.org/2000/svg">
<rect fill="${fill}" height="100%" stroke="${stroke}" stroke-width="${0}" width="50%" x="50%" y="0" />
<line stroke="${stroke}" stroke-width="${strokeWidth}" x1="50%" x2="50%" y1="0" y2="100%" />
</svg>
</h:svg>
<c:url value="/${id}w.png" var="bg" />
<td style="background: url(${bg});">
&nbsp;
</td>
<h:svg image="/${id}c.png" noCache="${noCache}">
<svg height="${r}" width="${r}" xmlns="http://www.w3.org/2000/svg">
<rect fill="${fill}" height="100%" stroke="${stroke}" stroke-width="${0}" width="100%" x="0" y="0" />
</svg>
</h:svg>
<c:url value="/${id}c.png" var="bg" />
<td style="background: url(${bg}); vertical-align: top;">
<jsp:text>
${body}
</jsp:text>
</td>
<h:svg image="/${id}e.png" noCache="${noCache}">
<svg height="${r}" width="${r}" xmlns="http://www.w3.org/2000/svg">
<rect fill="${fill}" height="100%" stroke="${stroke}" stroke-width="${0}" width="50%" x="0" y="0" />
<line stroke="${stroke}" stroke-width="${strokeWidth}" x1="50%" x2="50%" y1="0" y2="100%" />
</svg>
</h:svg>
<c:url value="/${id}e.png" var="bg" />
<td style="background: url(${bg});">
&nbsp;
</td>
</tr>
<tr style="height: ${r};">
<h:svg image="/${id}sw.png" noCache="${noCache}">
<svg height="${r}" width="${r}" xmlns="http://www.w3.org/2000/svg">
<circle cx="100%" cy="0" fill="${fill}" r="${r/2}" stroke="${stroke}" stroke-width="${strokeWidth}" />
</svg>
</h:svg>
<c:url value="/${id}sw.png" var="bg" />
<td style="width: ${r};">
<h:img src="/${id}sw.png" style="margin: 0; padding: 0; width: ${r}; height: ${r};" />
</td>
<h:svg image="/${id}s.png" noCache="${noCache}">
<svg height="${r}" width="${r}" xmlns="http://www.w3.org/2000/svg">
<rect fill="${fill}" height="50%" stroke="${stroke}" stroke-width="${0}" width="100%" x="0" y="0" />
<line stroke="${stroke}" stroke-width="${strokeWidth}" x1="0" x2="100%" y1="50%" y2="50%" />
</svg>
</h:svg>
<c:url value="/${id}s.png" var="bg" />
<td style="background: url(${bg});">
&nbsp;
</td>
<h:svg image="/${id}se.png" noCache="${noCache}">
<svg height="${r}" width="${r}" xmlns="http://www.w3.org/2000/svg">
<circle cx="0" cy="0" fill="${fill}" r="${r/2}" stroke="${stroke}" stroke-width="${strokeWidth}" />
</svg>
</h:svg>
<c:url value="/${id}se.png" var="bg" />
<td style="width: ${r};">
<h:img src="/${id}se.png" style="margin: 0; padding: 0; width: ${r}; height: ${r};" />
</td>
</tr>
</table>
</jsp:root>
TOMCAT/webapps/svgtest/WEB-INF/tags/page/simple.tagx
<?xml version="1.0" encoding="windows-1256" ?>
<!-- Copyright © 2005, Ghasem Kiani. License: GPL. -->
<jsp:root version="2.0" xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:jsp="http://java.sun.com/JSP/Page">
<jsp:directive.attribute name="title" />
<jsp:directive.attribute name="head" />
<html>
<head>
<meta content="text/html; charset=${pageContext.response.characterEncoding}" http-equiv="Content-Type" />
<meta content="no-cache" http-equiv="pragma" />
<c:if test="${not empty title}">
<title>
<c:out value="${title}" />
</title>
</c:if>
<jsp:text>
${head}
</jsp:text>
</head>
<body>
<jsp:doBody />
</body>
</html>
</jsp:root>
TOMCAT/webapps/svgtest/WEB-INF/web.xml
<?xml version="1.0" encoding="windows-1256"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee web-app_2_4.xsd">
<description>
SVG Test Web Application By Ghasem Kiani. This application uses the Apache Batik project to convert SVG graphics into PNG images.
</description>
<display-name>
SVG Test
</display-name>
<welcome-file-list>
<welcome-file>
index.jspx
</welcome-file>
<welcome-file>
index.html
</welcome-file>
</welcome-file-list>
</web-app>
P.S.
The better way to do this is to write a servlet that outputs the image stream to the client. This will not envolve the writing of the image on disk. Even we can map files with .svg
extension to that servlet, so that whenever we reference an SVG file on our application in the src
attribute of an <img>
tag, the servelt will be activated and the rasterized image will be sent instead. On the other hand, the JSP architecture seems to be inconvenient for outputting binary formats.
No comments:
Post a Comment