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':
?rotate=90
Let's be more adventuresome; let's rotate it a less obvious amount and change the color:
?rotate=-15;colour=0f0
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.
URI Arguments:
centre=Bool
    
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.
compact=Bool
    
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.
embed=Bool
    
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'.
fill=name
fill=xxx
fill=xxxxxx
    
Similar to 'color' except this sets the default value for filled regions (if you choose to use it, that is).
rotate=N
    
Specifies the number of degrees to rotate the image about its center. Negative values are permitted too.
scale=N
scale=xScale,yScale
    
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.
translate=N
translate=xAmount,yAmount
    
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.
Class Methods:
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.
float UIControl::distance(float x1, float y1, float x2, float y2)
    
Returns the undirected straight-line distance between the points.
mixed UIControl::intersection_line_line(array p1, array p2, array p3, array p4)
    
Returns a two-element x,y array for the point at which the lines cross – or null if they don't (are parallel).
float UIControl::mitreLength(float theta, float width)
    
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.
string UIControl::mkpath(array instructions, [string prefix], [bool collapse])
    
Take an array of path instructions (demonstrated earlier) and return a string suitable for inclusion in a <path> element's 'd' attribute.
string UIControl::mkpoly(array points)
    
Similar to UIControl::mkpath(), except that the argument is an array of points. The return value can be included directly into the 'points' attribute of a <polyline> or <polygon> element.
array UIControl::polar2rect(float r, float theta)
    
Given a coordinate in polar notation, returns a two-element array containing the corresponding rectangular notation.
float UIControl::slope(float x1, float y1, float x2, float y2)
    
Given two points, this returns the slope of the line that passes through them.
Instance Methods:
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.
string UIControl::fill_colour(void)
string UIControl::set_fill_colour(mixed newVal, [bool replace])
    
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.
float UIControl::height(bool scaled)
float UIControl::set_height(float newHeight, [bool replace])
    
Accesses or sets the workspace height. This value is used when calculating the dimensions.
float UIControl::opacity(void)
float UIControl::set_opacity(float newVal, [bool replace])
    
As fill_colour, except that the property affected is reserved for an opacity value.
void UIControl::record(void)
    
Begins capturing the output stream for processing by finalise().
float UIControl::rotation(void)
float UIControl::set_rotation(float newVal, [bool replace])
    
As opacity(), only for the degree of rotation.
array UIControl::scale(void)
array UIControl::set_scale(mixed newVal, [bool replace])
    
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.
string UIControl::stroke_colour(void)
string UIControl::set_stroke_colour(string newVal, [bool replace])

Ass fill_colour().

array UIControl::translation(void)
array UIControl::set_translation(mixed newVal, [bool replace])
    
As scale() except that the values represent the offset from the placement origin.
string UIControl::transforms(void)
    
Returns a string representation of all of the transform factors. For inclusion in an SVG element.
string UIControl::viewBox(void)
    
Returns a string for inclusion in the <SVG> element, which allows the object to be sized dynamically. The 'embed' argument must have been specified or the return string is empty.
float UIControl::width(bool scaled)
float UIControl::set_width(float newWidth, [bool replace])

As height().
    
We hope you've enjoyed this article on using PHP to create SVG images, and will begin experimenting with SVG images on your own!