As with many of the pages on my Web site, my SVG images are in fact generated by PHP scripts. I end up including many of the same images in other pages, but not always with the same size or colors, so making them customizable on a case-by-case basis is a useful thing. Attributes such as size, scale, colour, rotation, and position are obtained from arguments in the URL.
I like my code to be readable, so by default the output of the scripts is nicely formatted. However, that takes a lot of space so I included an option to compact it and reduce excess whitespace for easier and faster transmission over the Web -- or for use in places where it is not going to be read by a human. To maintain context and scope I developed a custom class for dealing with SVG output. It's still a work in progress, so please don't cringe at the code.
The first thing the script needs to do is emit the Content-Type header field:
The next thing the script needs to do is instantiate the class and create a script-specific object; the argparse.php file contains the classes and functions I'll be describing.
$o_ui = new UIcontrol($width, $height);
The width and height arguments are optional on object creation, but they're useful for later calculations such as sizing or scaling.
The two most complicated things the class does are handling the path SVG element and compressing the output to remove excess whitespace. The former is done by encoding the path instructions in an array of arrays (of arrays) and passing it to an object method. It's a bit difficult to describe so perhaps an example would work better:
Each element in the top-level array represents a single instruction, and is itself an array consisting of the instruction letter followed by one or more arrays of arguments for the instruction. Since one of the goals is to make the output readable, each path instruction is put on a line by itself, the arguments are formatted nicely and indented appropriately. The second argument to the mkpath method is in fact the number of spaces to indent in order to make the arguments line up nicely. By default all numeric arguments are formatted using a %7.2f printf format effector; this can be changed by altering the 'format' property of the UIcontrol object.
The other moderately complex thing the object may need to do is remove whitespace and reformat numbers to minimize transmission bytes. This is done after the SVG has been generated, by capturing all the output and parsing it before passing it on to the server for transmission to the client. There are special methods for this, too:
The capture is done with the ob_start(), ob_get_contents(), and ob_end_clean() PHP functions. If compact output has not been requested, the finalise() method simply prints what has been captured and exits. If compaction has been requested, however it takes the output, parses it into a DOMDocument, removes any 'description,' 'desc,' 'title,' and 'comment' elements, locates any 'path,' 'polygon,' and 'polyline' elements, parses the 'd' or 'points' attributes to remove excess white space, leading zeros, and trailing zeros; turns the final result back into XML â not preserving whitespace â and then prints it.
Naturally, recording has to start before the first text is emitted or the doctype is sent. It's okay to use the header function call before beginning the recording, since header fields aren't included as part of the document itself.
Image sizing can be controlled by specifying arguments in the URL. Width and height can be explicitly and independently set. The scale option can stretch or shrink the image in either direction, or both together. Specifying the 'embed' argument in the URL will cause the image to be sized to fit into whatever enclosing frame the viewer has for it. The image can be repositioned, rotated, and in some cases the foreground and background colours can be specified.
Of course, one of the disadvantages of using the URL to pass arguments is that the SVG files are unusable in a standalone environment; they need to be accessed through the Web in order for PHP to be invoked. You can get around this by accessing the SVG Web document and then saving the source.
Normally my SVG elements use fixed values for things like height, width, font size, radius, etc., and all the manipulations are performed by applying transforms to the enclosing 'g' (group) elements. In addition, the graphics are centered around the origin, so if you display them without using any positioning instructions, you'll only see the lower right quadrant â because the display origin of the screen is at the upper left corner. To make it easier to figure out how they should be positioned, I put the dimensions of the image (both scaled and unscaled) into the title, so when you display it in your browser, the actual size of the image is shown in the window's title bar. Then, in order to move it entirely onto the screen all you need to do is divide the dimensions in half and add X and Y positioning instructions to move the image over by that amount.
This article is an excellent opportunity to demonstrate the saying âa picture is worth a thousand words,â so let me give a couple of examples.
This is a very simple object -- no more than a circle and a line. In its basic form, the SVG code for this is:
Now we can manipulate it using the URI arguments. Let's rotate it 90Â°; the argument for that, surprisingly enough, is 'rotate':
Let's be more adventuresome; let's rotate it a less obvious amount and change the color:
You get the idea. I'm obviously not going to include all the code behind the class; you can pick that up on the Web site. However, here are the basic URI arguments and methods available, so you can get an idea of what the library can do in case you want to use it or get ideas from it.
Supported URI arguments are described below. Arguments are separated either by '&' or ';' -- I prefer ';' because links embedded in documents don't need to be escaped. 'Bool' values are interpreted according to PHP's rules.
Note the spelling: This is a shortcut means to getting the entire image onto the screen; it's equivalent to 'translate=X,Y' where X and Y are half the total width and height.' centre' is implied by 'embed' (below), and is disabled by 'translate'.
This sets the foreground color of the image. It may not always make sense; for example if the image is composed of multiple colors. Since in many cases the foreground is stroked, that's an alias for this argument name.
If true, this instructs the 'finalise()' method to remove excess whitespace and leading zeroes before passing the output to the server for transmission to the client.
If set to true, this enables the SVG document header viewBox attribute, which makes it much easier for image to be embedded in another for purposes of sizing and such. This implies 'centre=1'.
Similar to 'color' except this sets the default value for filled regions (if you choose to use it, that is).
Specifies the number of degrees to rotate the image about its center. Negative values are permitted too.
This allows resizing or stretching of the image. If a single value is provided, both the X and Y dimensions will be scaled by the same amount.
This controls how far from the placement origin image should be offset. In other words, it allows you to move the image around. Translation is performed before scaling, so you don't have to worry about calculating scale values for the translation amounts. Specifying this argument disables the 'centre' argument.
array UIControl::derive_line(array p1, array p2)
Given the line defined by the two points, this returns the slope and intercept â 'm' and 'b' from y = mx + b.
This is useful for calculating boundaries. Given an angle and a stroke width, it returns how much farther the mitre point extends. Huh? Look at the picture; this method returns the distance between the two vertical black lines.
Given two points, this returns the slope of the line that passes through them.
These methods are used to control an instantiation of the class, setting environmental factors such as rotation, etc., or returning strings for inclusion in the SVG code.
mixed UIControl::dimensions(bool asText)
Returns an array containing the dimensions of the current workspace, calculated from the width and height values. Both scaled and unscaled values are included. If 'asText' is true, the return value is a string suitable for inclusion in a <title> element.
Used to set or access the property for the default fill colour. A best effort is made to normalise the input to a correct colour value (e.g., #c0c0c0). If 'replace' is true, the default fill will only be set if it isn't already.
string UIControl::finalise(bool emit)
Wraps up the output recording and performs any required post processing (such as compression), and returns the result. If 'emit' is true (the default), the results will also be printed to the standard output stream.
As rotation(), except that the return value is a two-element array representing the scale factors for the X and Y dimensions. The input may be an array or a single number; in the latter case, it is used to scale both dimensions.