ImageMagick Workaround for SVG Support

I have installed the PECL package IMagick in my computer. Its current version 7:6.6.2.6 -1 does a great job converting SVG files into raster images. In my hosting account the Imagemagick package is not the most recent: I have an SVG file that contained elemants repeated using the Inkscape’s spray can, but when I convert it using a test program in my host it only print one of the repeated stars.

How can I resolve this?

Well, as you probably know, SVG is an XML format, so I can read it using a text editor. Doing this I found that there’s an element named ‘use’ with an attribute named ‘href’. “href” we know from HTML, and the element’s named “use” makes sense when using an existing element. So I commented out all the “use” elements, and viewing the SVG file as an image I saw the star at the exact location as in the  erroneous raster image, but with other elements not printed there. Probably the SAX engine stopped after the first unknown element. Commented out all elements from the first “use” elements until the end of file, and BINGO! This reproduced the problem. Well, the “use” elements should be replaced by the original ones with changes to their attributes. The following code does it:

/**
* I found that the "use" tags are not supported, so I'm replacing them
* by something else.
*
* The function returns the content of the file, the returned value can be the 1st argument of 
* Imagick::readImageBlob
*/
function svg_workaround($filename){
  $mimeType = mime_content_type($filename);

  if ($mimeType != 'text/xml' && mimeType!='image/svg+xml'){
  // At home the mime type is "Image/svg+xml" in the hosting account "text/xml"
    return file_get_contents($filename);
  }

  $contents = file_get_contents($filename);
  $docRoot = new DOMDocument(); // For random access and fast location of referenced objects
                                                     // you would use 'DOM'.

  if (!$docRoot->loadXML($contents))
    return FALSE;

  $xlinkNs = getXLinkNs($docRoot); // For the "href" attribute.

  $svgElems = $docRoot->getElementsByTagName('svg');
  if (!$svgElems->length)
    return $contents;

  $nodeList = $docRoot->getElementsByTagName('use');

  // Because the node lists changes when elements are replaces, we keep the elements in an array.
  $nodes = array();
  for ($i=0; $ilength; $i++)
    $nodes[]=$nodeList->item($i);

  foreach ($nodes as $node)
    if (!replace_use_tag($node, $docRoot, $xlinkNs))
      return false;

  return $docRoot->saveXML();
}

// The function that replaces the "use" elements
function replace_use_tag($node, $doc, $xlinkNs){
  $href=$node->getAttribute($xlinkNs . ':href');
  if (!$href)
    $href=$node->getAttribute('href');

  if (!$href)
    return FALSE;

  $id=substr($href, 1); // Removing the '#' from the href.
  if (!$id)
    return FALSE;
  // We use 'xpath' to locate a node with a given id because, unlike HTML, the use of getElementById
  // in XML DOM is not trivial.
  $xpath=new DOMXPath($doc);
  $elems=$xpath->query('//*[@id=\'' . $id . '\']');
  $orig=$elems->item(0);

  if (!$orig)
    return FALSE;

  $newNode=$orig->cloneNode(TRUE); // TRUE for deep cloning; When cloning a composite such as "g",
                                                             // its child elements should be copied as well.

  // Copy attributes from the 'use' node to the new node, not including the 'href'
  $attributes=$node->attributes;

  $length=$attributes->length;

  for ($i=0; $iitem($i); $i++){
    $attrName=$item->name;
    $attrValue=$item->value;
    if ($attrName=='href' || $attrName==$xlinkNs . ':href')
      continue;
    $newNode->setAttribute($attrName, $attrValue);
  }

  $parent=$node->parentNode;
  $parent->replaceChild($newNode, $node);
  return TRUE;
}

// Get the namespace for XLink.
function getXLinkNs($doc){
  return $doc->lookupPrefix('http://www.w3.org/1999/xlink');
}

Advertisement

Extending PHP

Extending PHP does not mean just adding classes and functions. It also means adding functionality not previously supported by PHP. This can be done by writing functions in C that can be called from PHP. These functions should be able to receive parameters passed from PHP. The difference between a variable in a C source and a variable in PHP is that in PHP the variable in PHP is loosely typed. That is, in PHP a variable can be used as an integer, but later as a string or a floating point number, so its equivalent in C is zVal. “zval” is a structure containing the variable’s type and a union made up of members of different types sharing the same memory address.

The PHP extension is a dynamically linked library (‘dll’ in Windows, ‘so’ in Linux) containing functions that can be called from PHP.

The process of creating an extension is described in the chapter “PHP at the Core: A Hacker’s Guide to the Zend Engine” in the famous PHP manual.

Building the extension starts with tunning the ‘ext_skel’ script, which creates a directory for your extension including a skeleton of the extension’s C code and a header file.

The next step is to add functions and global variables using macros.

The macro used for defining a  function is PHP_FUNCTION(function_name). Returning a value is done using the macros RETURN_TRUE, RETURN_FALSE, RETVAL_* . These macros are in /path/to/php_include_dir/Zend/zend_API.h

Arguments are passed to local C variables using the function ‘zend_parse_parameters’.

The next step is to edit config.w4(Linux) or config.w32(windows), then run ‘phpize’ and ‘configure’ to create a Makefile.

Finally, run make.

The dynamically loaded library will be created in the ‘modules’ directory. Use ‘make install’ with root permissions to copy your extension to the PHP extension directory.

Unfortunately, the guide is far from being complete, so to look for examples, browse ‘pecl.php.net‘ for source codes.