Generating Dojo GFX Drawings from SVG Files ||
Eric Brown was my first boss at SitePen and was one of my go-to vector graphic creation men. Eric created an awesome tool for converting SVG images to JSON, useful for creating vector graphics of all rendering engines (Canvas, SVG, VML, etc.) that Dojo's GFX library supports. The original script had a few issues, so Eric's here to clear those up.
This article builds on the earlier article Generate Dojo GFX Drawings from SVG Files. If you've not yet read that article, go and do it now. I'm not going to repeat what's already in it, and this article won't make a lick of sense without reading that one first. Really, go ahead, don't worry, go read it now. I'm a patient sort of guy and will still be here when you get back.
So you've read it now? Good. You understand that the technique being discussed here is a way of making images that'll handle resizing gracefully without quality loss, regardless of whether they're being shown on giant desktop monitors or minuscule phone displays. With this technique you get full retina quality without the full retina download. Now you've got got the basics down and are ready to learn a handful of new tricks.
Tip #1
The first is a work-around for a gotcha that may have bitten you on
some of the SVG images you tried to convert. Oftentimes when you do a
conversion there will be errors in the resulting JSON file that will cause
IE grief. Don't fret! This is a known limitation that has always been in
svg2gfx.xsl
, but it's trivially corrected post-conversion. All
that one needs to do is remove occurrences of ,]
and
,}
from the JSON and the problem is solved. This can be pretty
easily done with your favorite text editor, but it's also easily done with
a one-liner that works on all Linux systems, Macs, and other UNIX-like
machines. Starting with the file you converted per the earlier article,
type the following at the command line:
sed -e 's/,}/}/g' -e 's/,]/]/g' output.json > correctedoutput.json
This will make your JSON play nice with IE. (Yeah experts, I know this can be done more succinctly with more complicated regular expressions, but this version works with pretty much any SED.)
Tip #2
![[FTW Logo]](https://webarchive.library.unt.edu/web/20121223160659im_/http://davidwalsh.name/demo/ftw-eric.png)
This second trick is really easy but will get you better results. Say for instance you're making a logo for your new company, "FTW", and you want a few letters incorporated into the logo. Use whatever graphics tool you're using to generate outlines for the letters prior to conversion, and your logo will display properly on every machine regardless of what fonts they've got installed. See? I told you this one was simple.
Tip #3
This third trick is nearly as simple as the second one. If you put
id
values on objects within your source SVG, they'll get
carried through as name
values within the resulting JSON. This
can make debugging much easier should something go wrong. It also enables
easier manual tweaking after the conversion should it be desirable. Setting
id
values can sometimes be done directly within your drawing
application, but can also be done directly to the resulting SVG with a
text editor.
Tip #4
If you're using your image in just one page, there's a performance advantage to embedding it within the JavaScript for your page. This is easily done, and best shown by example. I'll walk through the entire process of using the FTW image above into an embedded resizable logo on a page.
<?xml version="1.0" encoding="utf-8"?> <svg version="1.2" baseProfile="tiny" id="FTW" xmlns="http://www.w3.org/2000/svg " xmlns:xlink="http://www.w3.org/1999/xlink " x="0px" y="0px" width="400px" height="400px" viewBox="0 0 400 400" xml:space="preserve"> <g id="FTWLogo"> <polygon id="Pentagon" fill="#1B1464" stroke="#000000" stroke-width="5" points="105.232,340.097 41.442,151.479 201.116,32.524 363.591,147.625 304.331,337.715" /> <polygon id="Star1" fill="#FFFF00" stroke="#FBB03B" stroke-width="2" points="212.992,48.754 200.967,42.751 189.205,49.255 191.198,35.963 181.377,26.787 194.635,24.574 200.328,12.399 206.528,24.324 219.867,25.976 210.441,35.558" /> <polygon id="Star2" fill="#FFFF00" stroke="#FBB03B" stroke-width="2" points="53.874,168.643 41.849,162.64 30.087,169.144 32.08,155.853 22.259,146.676 35.517,144.463 41.209,132.289 47.41,144.213 60.749,145.865 51.323,155.447" /> <polygon id="Star3" fill="#FFFF00" stroke="#FBB03B" stroke-width="2" points="377.663,163.829 365.638,157.826 353.876,164.33 355.869,151.039 346.049,141.862 359.306,139.649 364.998,127.475 371.199,139.399 384.538,141.051 375.112,150.633" /> <polygon id="Star4" fill="#FFFF00" stroke="#FBB03B" stroke-width="2" points="117.066,356.964 105.041,350.961 93.279,357.465 95.272,344.174 85.452,334.997 98.709,332.784 104.402,320.609 110.603,332.533 123.941,334.186 114.516,343.768" /> <polygon id="Star5" fill="#FFFF00" stroke="#FBB03B" stroke-width="2" points="317.365,353.679 305.34,347.676 293.578,354.18 295.57,340.889 285.75,331.712 299.008,329.499 304.701,317.324 310.901,329.248 324.24,330.9 314.814,340.482" /> <g id="FTW"> <path id="F" fill="#FFFFFF" d="M137.14,198.674h-28.076v27.392h-20.41v-67.675h48.486v13. 574h-28.076v13.086h28.076V198. 674z" /> <path id="T" fill="#FFFFFF" d="M215.754,174.846h-24. 805v51.22h-20.361v-51.22h-24. 805v-16.455h69.971V174.846z" /> <path id="W" fill="#FFFFFF" d="M337.287,158.391l-29.297, 68.554h-11.816l-17.578-39. 892l-18.018,39.892h-11.914l- 28.711-68.554h21.143 l14.062,37.5l16.113-37.5h14. 6l15.82,37.5l14.697-37.5H337. 287z" /> </g> </g> </svg>
Notice that per Tip #2 this SVG has had its FTW replaced with outlines.
In lieu of three letters, it has three paths. This effectively embeds the
font so it'll render the same everywhere. Also, per Tip #3 portions of
the drawing have been given id
attributes.
Now we do the full conversion in just one command line by first converting it as covered in the earlier article, and then piping it directly into the postprocessing step we discussed above in Tip #1:
xsltproc svg2gfx.xslt ftw.svg | sed -e 's/,}/}/g' -e 's/,]/]/g' > ftw.json
This will give us the GFX JSON we need. It'll all be in one line. We can now copy and paste this line into a page. We'll show it copied into a page as part of the next trick.
Tip #5
With just a little bit of clever hackery it's easy to work vector graphics magic into a site that features unobtrusive JavaScript. While virtually every modern environment both mobile and stationary will use the vector graphic version, folks without JavaScript will still see at least an appropriate bitmap image. The code below makes it happen; it's pretty heavily commented and thus speaks for itself. You can use it as a template for your own pages. Normally one would use a bitmap image that approximates the vector image, but here we're using two distinctly different images to make the workings of the trick more obvious.
<!DOCTYPE html> <html> <head> <title>FTW</title> <!-- We're embedding this CSS in here to make this demo easier. Normally you'd put this in its own file. --> <style> h1 { margin:1em; padding:1em; } #Logo { width:5em; height:5em; float:left; } </style> </head> <body> <!-- This bitmap image will only get displayed if the viewer has JavaScript turned off. For this example we've made it distinct for illustration purposes; when you're doing it for real, use a low-res bitmap version of your vector image for good graceful degradation. --> <img id="Logo" alt="FTW Logo" src="ftl.png"> <h1>This Company Is FTW!</h1> <p>Blah blah blah, blah blah.</p> <script data-dojo-config="async:true" src="//ajax.googleapis.com/ajax/libs/dojo/1.8.0/dojo/dojo.js"></script> <!-- We're embedding the following JavaScript in here to make this demo easier. Normally you'd put this in its own file. --> <script> require(["dojo/dom", "dojo/dom-construct", "dojo/dom-style", "dojo/behavior", "dojox/gfx", "dojox/gfx/utils", "dojo/domReady!"], // This code works on desktop browsers including recent versions of // Chrome, Safari, Firefox, Opera, and IE. It works on iOS. It works // on Android. It even works on some more exotic systems like the // OLPC XO. function(dom, domConstruct, domStyle, behavior, gfx, utils) { behavior.add({ '#Logo':function(node) { var logoNode=dom.byId('Logo'); var surfacePos=dojo.position(logoNode); var surfaceWidth=surfacePos.w; var surfaceHeight=surfacePos.h; // The following 400 comes from the size of our original SVG image. var scale=surfaceWidth/400; // We're going to make the bitmap placeholder image hidden. domStyle.style(node,{'visibility':'hidden'}); // Now we make a new div to take its place on the screen. var extraDiv=document.createElement('div'); domStyle.style(extraDiv,{'position':'absolute','display':'block', 'width':surfaceWidth+'px','height':surfaceHeight+'px', 'top':surfacePos.y+'px','left':surfacePos.x+'px'}); domConstruct.place(extraDiv,node.parentNode,'first'); // We put a GFX surface into this new div. var surface=gfx.createSurface(extraDiv,surfaceWidth,surfaceHeight); var logo=surface.createGroup(); // This next line has your JSON copied into it between the two ' var logoShape='[{name:"FTWLogo",children:[{name:"Pentagon",shape:{type:"polyline",points:[{x:105.232,y:340.097},{x:41.442,y:151.479},{x:201.116,y:32.524},{x:363.591,y:147.625},{x:304.331,y:337.715},{x:105.232,y:340.097}]},fill:"#1B1464",stroke:{color:"#000000",width:"5",style:"Solid"}},{name:"Star1",shape:{type:"polyline",points:[{x:212.992,y:48.754},{x:200.967,y:42.751},{x:189.205,y:49.255},{x:191.198,y:35.963},{x:181.377,y:26.787},{x:194.635,y:24.574},{x:200.328,y:12.399},{x:206.528,y:24.324},{x:219.867,y:25.976},{x:210.441,y:35.558},{x:212.992,y:48.754}]},fill:"#FFFF00",stroke:{color:"#FBB03B",width:"2",style:"Solid"}},{name:"Star2",shape:{type:"polyline",points:[{x:53.874,y:168.643},{x:41.849,y:162.64},{x:30.087,y:169.144},{x:32.08,y:155.853},{x:22.259,y:146.676},{x:35.517,y:144.463},{x:41.209,y:132.289},{x:47.41,y:144.213},{x:60.749,y:145.865},{x:51.323,y:155.447},{x:53.874,y:168.643}]},fill:"#FFFF00",stroke:{color:"#FBB03B",width:"2",style:"Solid"}},{name:"Star3",shape:{type:"polyline",points:[{x:377.663,y:163.829},{x:365.638,y:157.826},{x:353.876,y:164.33},{x:355.869,y:151.039},{x:346.049,y:141.862},{x:359.306,y:139.649},{x:364.998,y:127.475},{x:371.199,y:139.399},{x:384.538,y:141.051},{x:375.112,y:150.633},{x:377.663,y:163.829}]},fill:"#FFFF00",stroke:{color:"#FBB03B",width:"2",style:"Solid"}},{name:"Star4",shape:{type:"polyline",points:[{x:117.066,y:356.964},{x:105.041,y:350.961},{x:93.279,y:357.465},{x:95.272,y:344.174},{x:85.452,y:334.997},{x:98.709,y:332.784},{x:104.402,y:320.609},{x:110.603,y:332.533},{x:123.941,y:334.186},{x:114.516,y:343.768},{x:117.066,y:356.964}]},fill:"#FFFF00",stroke:{color:"#FBB03B",width:"2",style:"Solid"}},{name:"Star5",shape:{type:"polyline",points:[{x:317.365,y:353.679},{x:305.34,y:347.676},{x:293.578,y:354.18},{x:295.57,y:340.889},{x:285.75,y:331.712},{x:299.008,y:329.499},{x:304.701,y:317.324},{x:310.901,y:329.248},{x:324.24,y:330.9},{x:314.814,y:340.482},{x:317.365,y:353.679}]},fill:"#FFFF00",stroke:{color:"#FBB03B",width:"2",style:"Solid"}},{name:"FTW",children:[{name:"F",shape:{type:"path",path:"M137.14,198.674h-28.076v27.392h-20.41v-67.675h48.486v13.574h-28.076v13.086h28.076V198.674z"},fill:"#FFFFFF"},{name:"T",shape:{type:"path",path:"M215.754,174.846h-24.805v51.22h-20.361v-51.22h-24.805v-16.455h69.971V174.846z"},fill:"#FFFFFF"},{name:"W",shape:{type:"path",path:"M337.287,158.391l-29.297,68.554h-11.816l-17.578-39.892l-18.018,39.892h-11.914l-28.711-68.554h21.143 l14.062,37.5l16.113-37.5h14.6l15.82,37.5l14.697-37.5H337.287z"},fill:"#FFFFFF"}]}]}]'; // Now we can load the shape onto the surface. utils.fromJson(logo,logoShape); // This transform converts from our original SVG image size to // the size we need for our display. It's a vector transformation, // so there's no loss of quality. logo.setTransform(gfx.matrix.scale(scale)); } }); } ); </script> </body> </html>
Congratulations!
Now you're ready to go out and incorporate vector graphics for all your sites. You no longer have to just dream of having logos that render on any size display in crystal clear quality. You now have the power to make it happen.
great job sir david walsh…