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:
Header('Content-type: image/svg+xml; charset=utf-8');
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.
Include_Once('argparse.php'); $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:
$a_path = array(array('M', array(0, 0), ), array('l', array(-10, -10), ), array('h', array(20), ), ); $path = $o_ui->mkpath($a_path, ' '); print “==$path==n”; ==M 0.00, 0.00 l -10.00, -10.00 h 20.00==
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:
$o_ui->record(); : $o_ui->finalise()
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:
<circle cx="0" cy="0" r="10" stroke="#ff0000" stroke-width="2" fill="none" /> <line x1="-7.07" y1="-7.07" x2="7.07" y2="7.07" stroke="#ff0000" stroke-width="2" />
Let’s instrument that a little bit so that PHP can modify it according to the URL parameters. I also enclosed it in a group element so that it can be manipulated as a whole.
<g id="barred-circle"<?= $ui->transforms(); ?>> <circle cx="0" cy="0" r="<?= $radius; ?>" stroke="#ff0000" stroke-width="<?= $width; ?>" fill="none" /> <line x1="<?= $uleft; ?>" y1="<?= $uleft; ?>" x2="<?= $lright; ?>" y2="<?= $lright; ?>" stroke="#ff0000" stroke-width="<?= $width; ?>" /> </g>
The instrumentation isn’t going to do any good unless we provide values for it to work upon:
<?php $radius = 10.0; $width = ceil($radius / 5.0); $hwidth = $width / 2; $isect = sqrt(pow($radius, 2) / 2) ; $uleft = sprintf('%.2f', - $isect); $lright = sprintf('%.2f', + $isect); $nom_height = $nom_width = ($radius + $hwidth) * 2; ?>
Now let’s put it together:
<?php $radius = 10.0; $width = ceil($radius / 5.0); $hwidth = $width / 2; $isect = sqrt(pow($radius, 2) / 2) ; $uleft = sprintf('%.2f', - $isect); $lright = sprintf('%.2f', + $isect); $nom_height = $nom_width = ($radius + $hwidth) * 2; Include_Once('argparse.php'); $ui = new UIControl($nom_width, $nom_height); Header('Content-type: image/svg+xml; charset=UTF-8'); $ui->record(); print '<' . '?xml version="1.0" encoding="utf-8"?' . ">n"; ?> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"<?= $ui->viewBox(); ?>> <title>Barred circle (<?= $ui->dimensions(true); ?>)</title> <g id="barred-circle"<?= $ui->transforms(); ?>> <circle cx="0" cy="0" r="<?= $radius; ?>" stroke="#ff0000" stroke-width="<?= $width; ?>" fill="none" /> <line x1="<?= $uleft; ?>" y1="<?= $uleft; ?>" x2="<?= $lright; ?>" y2="<?= $lright; ?>" stroke="#ff0000" stroke-width="<?= $width; ?>" /> </g> </svg> <?php $ui->finalise(); ?>
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’.
colour=name | stroke=name colour=xxx | stroke=xxx colour=xxxxxx | stroke=xxxxxx
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.